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

174 KiB

Troubleshooting: Agent Providers And Setup

Agent runtime index · All troubleshooting

Provider discovery, installation, authentication, models, configuration, and runtime reachability.

Tutti Agent browser login succeeds but the desktop remains on the login screen

  • Symptom: Tutti account state is already visible in the desktop header and the browser login flow returns successfully, but the Tutti Agent surface does not react. Daemon logs show the token issue request succeeding, followed by tutti-agent login --with-tutti-llm-tokens failing because node cannot be found.
  • Quick checks: Compare the auth subprocess logs with the provider command resolution. A packaged npm launcher commonly starts with #!/usr/bin/env node; therefore finding the launcher binary is not enough. Check the resolved auth-command event for managed_node_configured=true and managed_node_on_path=true, then correlate it with the login process start and completion events. Never print token payloads while diagnosing this path.
  • Root cause: Tutti Agent sessions, status probes, and model discovery resolve the provider command through Tutti's managed-runtime resolver, which adds the bundled Node directory to the child environment. The dedicated auth bootstrap previously reused only a binary path and launched it with the daemon's ambient environment. On machines without a compatible system Node, /usr/bin/env could not execute the npm launcher even though the Rust program behind that launcher and the browser login itself were healthy.
  • Fix: Resolve the provider command and full managed-runtime environment at each auth entrypoint, then pass that same environment to the login subprocess. Keep the canonical Tutti Agent auth-home overrides authoritative. Do not special-case a guessed Node path or copy only TUTTI_APP_NODE: npm launchers consume PATH, and the shared resolver owns its construction.
  • Validation: Remove system Node from PATH, retain only Tutti's managed Node in the provider resolution, and execute an npm-style /usr/bin/env node launcher. Verify browser callback, model discovery, and session preparation all create ready auth material and leave the UI. Repeat provider command/environment resolution tests on Windows; on macOS/Linux retain an executable shebang integration test.
  • References: auth_bootstrapper.go service.go wiring_daemon_api.go

A cold Agent Extension handoff fails at acp session/new timed out after 30s

  • Symptom: A handoff from another Agent reaches an enabled Agent Extension, but the destination conversation fails before its first Turn with acp session/new timed out after 30s. This was observed with Hermes on a busy Windows machine even though the same installation starts successfully on a later attempt.
  • Quick checks: Correlate the destination session with any hidden live-model discovery session for the same provider. If initialize succeeds, session/new is written, provider stderr continues showing cold-start work, and there is no JSON-RPC response before the exact 30-second boundary, treat it as a startup budget failure rather than a malformed handoff payload. Check separately for authentication, runtime exit, and invalid-path errors.
  • Root cause: Interactive Agent Extension sessions inherited the generic standard-ACP 30-second initialize/session-new bound. A cold managed runtime can still be loading provider configuration and discovering models at that boundary, especially when model discovery and other Agent sessions contend for host resources. The handoff only exposed that slow cold start; it did not corrupt the destination prompt.
  • Fix: Give adapters resolved through the Agent Extension boundary a bounded 60-second initialize/session-new budget. Keep generic ACP providers on their existing default, keep foreground composer discovery bounded independently, and do not retry a timed-out session/new: the provider may have created a session even when its response was not observed. Apply the policy to Agent Extensions declaratively rather than checking a Hermes provider ID.
  • Validation: Verify the Agent Extension resolver projects the larger startup budget, then repeat a cold handoff on Windows and confirm the destination session reaches its first Turn without a 30-second timeout. Confirm unrelated standard ACP adapters retain their default and no duplicate session/new is sent.
  • References: runtime_resolver.go standard_acp_session.go

A fresh Hermes session waits on its first terminal or browser command

  • Symptom: Hermes starts normally, but the first terminal-backed capability in each new macOS session appears stuck for tens of seconds or minutes. A comparable Claude Code command opens the browser immediately, and Windows Hermes does not show the delay.
  • Quick checks: In the daemon's captured ACP stderr, find tools.tirith_security: tirith not found and compare it with tirith installed or tirith download failed. Attribute only that interval to the helper download; separately measure the following tutti browser open command. Check the session's effective HERMES_HOME, because a binary under another session home is not reusable.
  • Root cause: Hermes enables its Tirith pre-execution scanner on supported macOS/Linux targets and downloads the helper into $HERMES_HOME/bin. Windows currently has no Tirith build and Hermes skips that download. Tutti intentionally gives each Agent Extension session an isolated home; without an explicit shared directory declaration, every fresh Hermes home repeats the download. This can look like a browser regression when GitHub Release delivery becomes slow even though the actual browser command remains fast. Merely creating an empty stable $HERMES_HOME/bin is insufficient: previously downloaded helpers may still exist only in older session homes.
  • Fix: Keep the session home, config, credentials, skills, and state isolated. Let the signed extension declare runtimePrep.home.sharedDirs: ["bin"]; the provider-neutral preparer projects that directory from the stable source home into every session. If the stable directory is empty, seed it from the newest same-provider legacy session whose manifest owns the declared extension home; never overwrite an existing stable entry. Do not hardcode Hermes, Tirith, a version, a download URL, or a session ID in Tutti, and do not disable the upstream security scanner merely to hide latency.
  • Validation: Start with an empty stable directory and a manifest-owned legacy session cache; verify the next preparation seeds and projects that cache. Then create two fresh Hermes sessions on macOS and verify both session bin paths resolve to the stable source-home bin, neither first terminal command logs a Tirith download, and deleting either session does not remove the helper. On Windows, verify directory projection succeeds through a symlink or junction and Hermes continues using its upstream unsupported-platform fallback without a download.
  • References: agent-runtime-preparation.md extension_runtime.go

Hermes is ready but a new Windows session reports Agent failed to start

  • Symptom: Hermes passes setup detection, but the new-conversation model picker is empty and sending the first message reports Agent failed to start.
  • Quick checks: In the daemon log, compare setup-probe configuration with the session-scoped HERMES_HOME. The failing session typically reports no .env in its .tutti*/agent/runs/<session>/hermes directory, followed by ACP session/new returning No LLM provider configured. Check whether the working user config is under %LOCALAPPDATA%\hermes while %USERPROFILE%\.hermes is absent.
  • Root cause: The signed extension profile uses the portable default .hermes, but the shared runtime preparer previously resolved it only relative to the Windows user profile. Hermes itself uses the native Windows user cache location, so discovery could see credentials while the isolated session home copied none.
  • Fix: Keep an explicit HERMES_HOME as the highest-priority source. Otherwise, preserve an existing %USERPROFILE%\.hermes; when it is absent, resolve the resolve the portable leading-dot directory through the Windows user cache root first, then fall back to a migrated %USERPROFILE%\.hermes only when the native directory is absent. Copy only the files declared by the signed runtime-preparation profile. Keep this behavior in the platform adapter rather than branching on acp:hermes.
  • Validation: With no user-level HERMES_HOME, place credentials in %LOCALAPPDATA%\hermes, create a new session, and verify the model picker is populated and the first message starts successfully. Confirm an explicit source environment variable still takes precedence, and that a migrated %USERPROFILE%\.hermes is used only when the native directory is absent.
  • References: agent-runtime-preparation.md windows-platform-support.md extension_runtime.go

Focusing a workspace repeatedly starts provider CLIs and raises CPU usage

  • Symptom: Focusing or reopening a workspace makes the fan ramp up even when no Agent turn starts. The effect grows when more managed provider CLIs are installed.
  • Quick checks: Inspect GET requests to /v1/agent-providers/status. A focus-driven request with refresh=true bypasses the daemon status cache. Correlate the same time range with short-lived codex, opencode, cursor, node, git, and credential-helper processes. Use pnpm perf:agent-gui -- --scenario provider-status-focus-refresh --all-process-time-profile on macOS when a Chromium trace cannot see daemon child processes.
  • Root cause: Window lifecycle analytics requested a fresh availability snapshot by using the explicit-refresh path. Every pageview opportunity therefore bypassed the daemon cache and launched provider detection work; a focus arriving during a scan queued another forced scan.
  • Fix: Let availability analytics reuse the renderer's loaded status snapshot, and route only stale visibility reconciliation through a non-forced tuttid read. Let tuttid own cache expiry, credential fingerprints, and per-provider single-flight. Initialize providers in parallel for first-paint latency, but serialize later background stale reconciliation and cap the aggregate auth/version/adapter subprocess count. Keep refresh=true only for explicit user refreshes and provider-scoped confirmation after state-changing actions. A forced read must recheck auth/readiness, but can reuse successful executable-scoped facts while the binary fingerprint is unchanged; never cache failed version or adapter probes. Prefer validated credential files for OpenCode and Tutti Agent and allow at most one CLI fallback for malformed files. Reuse fields from a provider command instead of launching duplicate probes: Cursor about --format json returns both authentication details and cliVersion, so its status detection must not also launch cursor-agent --version unless the about output omitted the version.
  • Validation: Dispatch two focus events and assert they start no provider-status request, availability pageviews are unchanged, and an explicit refresh still sends exactly one forced request. The all-process trace should contain no focus-driven provider-detection child process burst.
  • References: agent-gui-node.md desktopAgentProviderStatusService.ts agent-provider-status-performance-scenario.mjs

Missing optional Agent CLIs inflate environment failure analytics

  • Symptom: agent.env_detected reports many cli_not_found outcomes for providers the user never selected, and an analytics view that treats every non-ready status as a failure makes the Agent environment failure rate look much higher than actual launch failures.
  • Quick checks: Group agent.env_detected by provider, reason_code, and cli_installed. If the dominant rows are cli_not_found with cli_installed=false across several managed providers for the same users, compare them with the desktop's background all-provider status request before investigating installation.
  • Root cause: Desktop intentionally discovers every managed provider so Agent pickers and setup surfaces can show local readiness. The automatic environment reporter previously treated every changed status as failure telemetry, so the expected absence of an optional CLI crossed the same reporting boundary as a provider that was installed but failed its runtime probe.
  • Fix: Keep cli_not_found in the canonical provider-status snapshot and continue full catalog discovery, but exclude the exact not_installed + cli_not_found + cli.installed=false state from automatic agent.env_detected reporting. Preserve explicit consent-gated problem reports and continue reporting installed-provider failures such as acp_adapter_launch_failed.
  • Validation: Reconcile one missing optional provider and one installed provider with a runtime launch failure in the same response. Assert both remain visible in the status snapshot while only the launch failure produces agent.env_detected.
  • References: desktopAgentProviderStatusDiagnostics.ts desktopAgentProviderStatusService.test.ts

Loading Agent Targets repeatedly starts Extension CLIs

  • Symptom: Opening a workspace, standalone Agent window, or Agent settings takes longer as more Agent Extensions are enabled. Repeated GET /v1/agent-targets requests start the same Extension CLI --version commands even though the executables did not change.
  • Quick checks: Time several authenticated GET /v1/agent-targets requests and correlate them with short-lived Extension CLI processes. Compare the total request duration with each configured discovery candidate's version command.
  • Root cause: Extension Target availability resolved every runtime from scratch. Package and executable validation are required, but the stable successful version result was discarded after every request. Concurrent catalog consumers could also run the same version command independently.
  • Fix: Cache only successful version results by resolved executable fingerprint, arguments, and version constraint, and coalesce concurrent probes. Keep failed probes uncached. Continue package authority and managed-runtime integrity checks on every resolution; catalog availability is not runtime launch authorization.
  • Validation: Resolve the same Extension runtime twice and assert one version subprocess. Cover concurrent callers, a failed probe followed by recovery, and executable replacement invalidation. Repeated /v1/agent-targets requests should retain the same availability while no longer starting unchanged Extension CLIs.
  • References: agent-extensions.md manager.go runtime_version_cache.go

A managed Extension runtime cannot be reused after an update on Windows

  • Symptom: The first managed Extension install reaches ready, but after Extension metadata changes the same compatible runtime falls back to not_installed only on Windows. Logs or focused tests report that renaming the legacy runtime directory failed because it is being used by another process.
  • Quick checks: Confirm the package version, discovery profile, and runtime identity are still compatible. Then inspect the adoption path around the legacy-directory rename; if the source managedRuntimeDirectory is still open, the failure is a Windows sharing violation rather than a package incompatibility.
  • Root cause: POSIX permits renaming a directory while the process retains an open directory handle. Windows does not. Keeping the verified candidate handle open across rename made the portability test pass on macOS while the native Windows lane rejected the same adoption.
  • Fix: Verify and fingerprint the candidate, write the new activation, close the source directory handle, rename it, then reopen the promoted directory and repeat the integrity check. Rollback must close the promoted handle before renaming it back and restoring the previous activation.
  • Validation: On native Windows, install a managed npm runtime, move it to a legacy identity, update the Extension without changing its runtime contract, and assert the runtime is adopted without reinstalling. Keep the batch-launcher execution and companion reconciliation assertions in the same Windows workflow.
  • References: windows-platform-support.md managed_runtime.go setup_test.go

Workspace Apps repeatedly probe extension authentication

  • Symptom: Several Workspace Apps opening together repeatedly start the same Extension ACP process. A logged-in target can intermittently become unavailable when duplicate setup probes exhaust the caller timeout.
  • Root cause: Older Apps consume only the broad Agent catalog, while newer Apps refine an exact target. If the broad catalog exposes installation readiness without authentication, old Apps can also show an unconfigured extension as usable.
  • Fix: Resolve installed extension authentication for broad and exact agent list requests, run broad probes concurrently, coalesce them by workspace and target in the daemon, and retain the result for a short bounded interval. Preserve auth_required as the canonical reason code even when the runtime supplies a more specific diagnostic reason. Explicit refresh bypasses the short cache.
  • Validation: Broad and exact-target requests within the cache window should share one setup probe per target. A ready Kimi target reports available; an unconfigured Hermes target reports unavailable with auth_required in both response shapes. A refreshed broad request probes each installed extension once.

An extension Agent is installed in the terminal but Tutti cannot detect it

  • Symptom: Running the Agent CLI in an interactive terminal works, while the extension setup dialog reports that no compatible runtime is installed.
  • Quick checks: Resolve the executable with command -v in the terminal and compare its parent directory with the desktop daemon's effective PATH. Check the signed extension profiles/discovery.json for a matching user-relative searchPaths entry and confirm that the reported CLI version satisfies its constraint.
  • Root cause: The desktop daemon inherits the GUI process environment and does not source .zshrc or other interactive shell startup files. Some official installers place a self-contained CLI in a vendor directory under the user's home, so the terminal can find it only after shell initialization.
  • Fix: Declare the vendor-owned location in the extension candidate, for example {"scope":"user","path":".vendor/bin"}. Keep the vendor path out of core and let the shared runtime resolver prepend the validated directory. Update the extension version constraint and managed package recipe to match the same official CLI generation.
  • Validation: Run discovery with a daemon PATH that omits the vendor directory and an injected user home containing the CLI. Assert that the binding is local, uses the absolute vendor executable, passes the version constraint, and retains the declared ACP launch arguments. Also reject absolute paths, parent traversal, and unsupported scopes.
  • References: agent-extensions.md runtime_contract.go manager.go

Clicking provider login repeatedly opens terminals and browser auth pages

  • Symptom: One login click opens many terminal nodes and repeatedly launches the provider's browser authentication page. The repeats may continue at the provider-status polling interval and resume after reopening the app.
  • Quick checks: Count agent-provider.terminal-command.start events for one provider and compare their timestamps with provider status requests. If every status snapshot is followed by another login command, inspect whether a React effect reattaches the setup workflow when a status-derived callback changes identity. Also check whether reattachment resets the request-sequence dedup marker and whether login is excluded from pending/single-flight tracking.
  • Root cause: The panel lifecycle and the setup workflow had competing ownership. A status refresh replaced the status object, rebuilt the login callback, reran the effect, reset wizard state, and accepted the same automatic login again. Each command opened a new terminal; the CLI then opened another browser page. A single terminal-handle map entry also allowed newer launches to overwrite the only handle that could later be closed.
  • Fix: Inject the panel-open host command into AgentGUI and let a window-scoped Agent Env service/controller own the request session, automatic-action idempotency, reveal/report state, and provider-status subscription. Route account and CLI login through the provider-status service. Its per-provider login lifecycle must reuse automatic requests, coalesce launches, replace an awaiting attempt only for an explicit retry, reject stale terminal handles, and own one poll. React should only subscribe and forward commands.
  • Validation: Replay at least 60 provider-status ticks for one request and assert one automatic login call. Cover rapid user clicks, explicit replacement, delayed terminal resolution, account login without a terminal, timeout/dispose cleanup, and a new request sequence. Verify one terminal and one browser page for the original click.
  • References: agent-gui-node.md desktop-layering.md agentEnvService.ts desktopAgentProviderLoginLifecycle.ts

Codex /status shows a 5h limit for a weekly-only account window

  • Symptom: Opening /status before starting a Codex conversation labels the only quota as 5h limit, while the upstream usage response reports a seven-day window. An active conversation may show a different label.
  • Quick checks: Inspect agent.usage_probe.result in desktop logs, then inspect the Codex /wham/usage response shape. If primary_window.limit_window_seconds is 604800 and secondary_window is absent, the primary slot is carrying the weekly window. Compare this with daemon app-server telemetry, where the same duration is windowDurationMins: 10080.
  • Root cause: Empty-session /status loads account quotas through the desktop provider probe, while active sessions receive canonical runtime usage from the daemon. Both paths once inferred quota type from primary/secondary position, but Codex may put the weekly-only quota in primary.
  • Fix: Classify known Codex windows by duration in both mappers: five hours is session, seven days is weekly. Use the positional type only when duration is missing or unknown. Keep additional named rate limits typed as model.
  • Validation: Cover a desktop probe response whose primary and secondary durations are opposite their conventional positions, plus daemon mapper cases for a weekly-only primary window. Verify both empty and active /status views.
  • References: agentProviderUsageProbe.ts codex_appserver_event_state.go

Provider setup notice flashes after switching to an already-connected agent

  • Symptom: Opening or restarting Tutti, then switching to an existing Claude Code, Cursor, or other managed-provider session, briefly shows the toast-like "connect provider before sending" notice even though automatic readiness recovery succeeds and messages can be sent after the status refresh settles.
  • Quick checks: Compare the desktop provider-status snapshot for the active provider with the AgentGUI view model. An active conversation must project no provider-readiness gate. If provider checking, auth_required, or not_installed disables its composer or renders a setup notice, catalog readiness leaked into session recovery ownership.
  • Root cause: Startup or daemon restart may temporarily expose an uncaptured or stale provider catalog status. AgentGUI projected that target-creation readiness into an already-open session, creating a second owner beside canonical session/runtime recovery. Transient catalog reconciliation then blocked the active composer and rendered a misleading connect action.
  • Fix: Keep the structured readiness gate only on the empty new-conversation surface. Active sessions always project a null provider gate; canonical session/runtime state owns recovery, submit, queue, and cancel capability. Remove active setup notices and all composer conditions derived from provider catalog readiness. Desktop may still refresh stale catalog status for future session creation.
  • Validation: Cover active-session null gate, empty-surface gate selection, and explicit install/login action mapping. Also run desktop readiness-gate tests, AgentGUI tests, and desktop/AgentGUI typechecks.
  • References: agentGuiProviderReadiness.ts useAgentGUIViewAssembly.ts useDesktopAgentGUIReadiness.ts desktopAgentProviderNotReadyRecheck.ts agent-gui-node.md

Agent provider picker shows only Claude Code and Codex

  • Symptom: Desktop settings, App Center, Issue Manager, or an installed workspace app only shows Claude Code and Codex even after Cursor/OpenCode are enabled.
  • Quick checks: For host-owned pickers, compare /v1/agent-targets with /v1/agent-providers/status. The target list must include enabled local:cursor/local:opencode, and provider status must report them as ready. For workspace apps, inspect the app server's provider detection; host preferences are not injected into app-owned provider lists.
  • Root cause: Host-owned app/workbench pickers are derived from daemon agent targets plus provider readiness and visibility preferences. The desktop default provider preference is a separate OpenAPI/event schema enum. Workspace apps own their runtime provider policy through @tutti-os/agent-acp-kit, so generated or packaged app UIs can still be limited to the providers the app implements.
  • Fix: Keep the host default-provider enum, desktop settings options, daemon validation, and generated clients/protocol schemas in sync. For installed workspace apps, update the app's provider detection/runtime integration instead of expecting host settings to expand the app UI.
  • Validation: Run pnpm generate:api, pnpm generate:event-protocol, pnpm check:api-generated, pnpm check:event-protocol-generated, desktop typecheck, and focused daemon preferences/API tests. If local pnpm resolves to the wrong version inside generator subprocesses, run the checks with a temporary pnpm PATH shim that delegates to corepack pnpm@10.11.0.
  • References: core.ts model.go tuttid.v1.yaml workspace-app-runtime.md

Claude composer model list stays stale after credential switch

  • Symptom: After an external credential switcher rewrites Claude Code auth or config files, the AgentGUI composer still shows the previous model list even though tuttid.log contains agent.model_catalog.invalidated for claude-code.
  • Quick checks: Search tuttid.log for CLAUDE_MODEL_CATALOG_INVALIDATION_DEBUG. If live_composer_models_invalidated is followed by running_session_model_options_reused, inspect that session's createdAtUnixMs and updatedAtUnixMs against the invalidation timestamp.
  • Root cause: Claude composer model discovery reuses model options from a live Claude runtime session to avoid spawning overlapping credential-touching processes. After a credential switch, a pre-switch runtime session can still carry the old runtimeContext.configOptions; reusing it repopulates the just-cleared live model cache with stale models.
  • Fix: Track provider model-catalog invalidation time in tuttid. When loading Claude composer options, skip running-session model options whose session timestamp is older than the provider invalidation, and allow hidden live discovery to query the current credentials.
  • Validation: Add daemon service coverage where invalidation happens after a Claude session has advertised old model options; the next composer options request must start hidden discovery and return the freshly discovered model list. Run cd services/tuttid && go test ./service/agent.
  • References: composer_live_model_discovery.go composer_live_model_cache.go

Cursor keeps the previous account after login changes

  • Symptom: A Cursor Agent session started under one account still reports that account's entitlement after cursor-agent login switches ~/.cursor/cli-config.json to another account. A fresh session may use the new account while the existing conversation does not.
  • Quick checks: Compare the cursor-agent process start time with the login attempt and the later prompt. If the same process accepts prompts both before and after the credential file changed, provider status refresh alone did not replace the credential-bearing ACP connection.
  • Root cause: Provider readiness and model-cache invalidation do not mutate an already running ACP process. Cursor reads its credentials at process startup, so the live connection retains the previous account until it is reprepared.
  • Fix: Watch the Cursor auth marker by full content fingerprint. Mark that provider's live connections stale without interrupting active Turns; the next ordinary idle send uses Host's atomic runtime reprepare-and-send operation, preserving the canonical Session, provider session id, and history.
  • Validation: Use a fake Cursor ACP process that records the auth generation at startup. Replace the marker through a native-path rename, then prove the same canonical session's next idle prompt runs in a new process. Also cover identical-content replacement, another provider, an active Turn followed by idle retry, and a second auth change racing with reprepare.
  • References: provider_auth_watcher.go provider_auth_runtime_rebind.go

Claude SDK context window shows 200k for 1M models

  • Symptom: Claude Code GUI usage shows a 200k context window for a model that should have 1M context, such as Claude Sonnet 5. The inverse can also happen after a model switch: a 200k model such as Haiku keeps showing the prior 1M total.
  • Quick checks: Inspect the session runtime context for usage.contextWindow.totalTokens, then trace the Claude SDK sidecar usage_updated payload and daemon agent_session.claude_sdk.usage_update log. If the payload keys include modelUsage but raw_total_tokens is 0, the daemon did not parse the model-usage context window. If previous_context_model and current_context_model differ but current_total_tokens equals previous_total_tokens, daemon usage normalization reused a stale context window across models. If switching models without sending a message makes the usage entry disappear, inspect whether a forced session-control reload returned runtimeContext without usage and replaced the active control state.
  • Root cause: AgentGUI only renders runtimeContext.usage; the total comes from the daemon and Claude SDK sidecar. Claude SDK result messages expose model usage as a map keyed by model id, for example modelUsage["claude-sonnet-5"].contextWindow. If either sidecar or daemon only parses array-shaped modelUsage, the context-window total is missing and daemon normalization falls back to 200k.
  • Fix: Parse modelUsage recursively as both arrays and maps before using fallback context-window values. Track the model associated with a cached context window, and only reuse the previous total for the same model or when the model is unknown. Treat runtimeContext.usage as incremental telemetry in AgentGUI reload races: a full session-control snapshot that omits usage should not clear the previous usage display. Do not hard-code alias-to-model mappings in Tutti.
  • Validation: Add sidecar and daemon coverage with map-shaped modelUsage carrying contextWindow: 1_000_000, plus daemon coverage for Haiku -> Sonnet5 -> Haiku usage updates where the last payload lacks totalTokens. Add AgentGUI coverage for session-control reloads that omit runtimeContext.usage. Then run the Claude SDK sidecar tests, daemon Go tests, AgentGUI tests, and typechecks.
  • References: main.ts main.test.ts claude_sdk_adapter.go

Bun-installed Codex works in a terminal but Tutti cannot use it

  • Symptom: codex --version works in an interactive terminal after bun add -g @openai/codex, while the desktop reports cli_not_found, codex_platform_pkg_incomplete, or a generic app-server launch failure.
  • Quick checks: Compare the terminal's command -v codex and bun pm bin -g with the daemon's effective PATH. Then run the exact resolved command with app-server and verify the formal initialize response followed by the initialized notification. Do not infer runtime failure from the absence of npm's nested @openai/codex/node_modules/@openai/codex-<platform> path: inspect the package root and ancestor node_modules directories as well.
  • Root cause: Desktop processes do not source interactive shell startup files. Bun's default global binary directory is ~/.bun/bin, BUN_INSTALL can relocate the Bun installation, and globalBinDir can point somewhere else entirely. Bun may also hoist the Codex platform package or place it behind an isolated .bun store, so an npm-nested-only check can reject a working CLI. Finally, the actionable missing-optional-dependency message is written to stderr after the app-server process starts; classifying only the protocol error loses that evidence.
  • Fix: Resolve Codex from the effective PATH, BUN_INSTALL/bin, the default ~/.bun/bin, and, only when those fail, a bounded bun pm bin -g discovery. Cache a successful configured-bin result by the Bun executable fingerprint. Rewrite the provider command to the resolved absolute Codex launcher and pass the same environment to status probes and real sessions. Treat a completed app-server initialize handshake as the readiness source of truth. Scan the package from the resolved launcher using Node-style ancestor node_modules lookup across npm, Bun, and pnpm layouts; layout evidence must never override a successful protocol probe. Classify the current platform package from bounded stderr even when the message says Missing optional dependency without an ENOENT token. Preserve Bun/pnpm provenance and never run npm repair in those package-manager-owned directories.
  • Validation: Use a minimal GUI PATH (/usr/bin:/bin) and cover default ~/.bun/bin, BUN_INSTALL/bin, and a custom bun pm bin -g result. Include a real single-process initialize/initialized handshake, Bun hoisted and isolated package layouts, missing optional-dependency stderr without ENOENT, unsupported app-server, and a broken Bun install that must not invoke npm.
  • Multiple installations: Do not stop at the first PATH result. Enumerate PATH, Bun, pnpm, npm, and Homebrew launchers; deduplicate logical package roots; then validate each launcher's version, package layout, and app-server handshake. A single ready candidate is used implicitly. Two or more ready candidates are not ranked by PATH or package-manager order: Tutti blocks startup and asks the user to choose one in the Agent environment panel. Persist that choice as its absolute launcher path (not an ephemeral list id), selected from the current catalog revision. A stale, broken, or unsupported saved choice also requires a new user selection; never silently fall back to another installation or run an installer. Read GET /v1/agent-providers/codex/runtime-candidates before changing it with PUT /v1/agent-providers/codex/runtime-selection.
  • References: resolver.go codex_bun_discovery.go codex_layout_scan.go codex_protocol_probe.go codex_appserver_probe.go

Codex npm install misses the platform package

  • Symptom: The Codex environment dialog says the CLI is installed, but the adapter or codex app-server probe is still missing. Logs may show Missing optional dependency @openai/codex-darwin-arm64, a long wait on an npm registry, or a later repair failure such as ENOTEMPTY while moving an existing @openai/codex directory. Another form is an immediate launcher failure such as env: node: No such file or directory after the JavaScript codex shim has been installed.
  • Quick checks: Inspect the npm debug log under the install cache for reify failed optional dependency, then check whether the matching platform package directory contains both package.json and the vendor codex executable. Compare the selected registry with a temporary prefix/cache install before changing the user's real install.
  • Root cause: @openai/codex installs a JavaScript launcher plus a per-platform optional package such as @openai/codex-darwin-arm64. npm can exit successfully even when an optional dependency fetch failed, which leaves the launcher installed but unable to start. A registry can also be reachable but too slow for the platform tarball, so retrying the same source burns the install timeout before mirrors are tried. The launcher itself uses #!/usr/bin/env node, so daemon-run Codex commands (--version, login, and app-server) need a usable Node on PATH. Tutti should prefer the user's Node environment, but fall back to the managed Node runtime when the visible codex shim exists and no user Node is resolvable.
  • Fix: Keep Codex installs on the Tutti-managed Node/npm runtime, install with optional dependencies included, and rank configured npm registries with a lightweight package metadata probe before attempting the install. Preserve TUTTI_AGENT_NPM_REGISTRY as an explicit single-registry pin with no mirror fallback. Provider command resolution should leave the user's Node first when it is available, and only append managed Node runtime env (TUTTI_APP_NODE, TUTTI_APP_NPM, managed PATH) when user Node is missing. Ensure the Codex app-server adapter consumes that provider command resolution; otherwise status probes can pass while session startup still fails with env: node: No such file or directory. A failed codex app-server probe is diagnostic evidence, not repair authorization by itself. Require a fresh, platform-specific missing-dependency classification plus a package-relative scan that confirms the same package or binary is missing. Keep npm repair limited to npm-owned layouts; Bun and pnpm installations must be repaired by their owning package manager rather than overwritten in place by npm. If the supported CLI and platform binary are complete but the formal protocol handshake still fails, report a runtime bug and do not reinstall.
  • Validation: Reproduce in a temporary prefix/cache using the Tutti-managed npm. Confirm codex --version, the platform package metadata and vendor binary, and a short codex app-server probe before touching the user's real install. Include a case where the visible codex shim uses #!/usr/bin/env node and the normal user PATH does not contain node.
  • References: npm_registry.go installer_codex_cli.go codex_platform.go provider_resolution.go codex_appserver_adapter.go

Tutti Agent npm install misses the platform package

  • Symptom: The Tutti Agent provider setup reaches the login screen or reports the CLI as installed, but tutti-agent login or tutti-agent app-server fails with Missing optional dependency @tutti-os/tutti-agent-<platform>.
  • Quick checks: Check the selected registry for both @tutti-os/tutti-agent and the exact alias target version, such as @tutti-os/tutti-agent@0.0.1-darwin-arm64. Do not treat a successful aggregate package metadata fetch as proof that the platform tarball is available.
  • Root cause: @tutti-os/tutti-agent follows the Codex npm layout: a JavaScript launcher plus per-platform optional dependencies expressed as npm aliases. npm can complete the aggregate install even when a mirror has not synced the platform optional dependency version.
  • Fix: Keep the package layout aligned with Codex and use registries that carry the platform optional dependency versions. The daemon default chain intentionally excludes mirrors that only sync the aggregate package. Preserve TUTTI_AGENT_NPM_REGISTRY as an explicit single-registry pin with no fallback. Before a managed global npm retry, remove only the selected package's sibling staging directories (for example, @tutti-os/.tutti-agent-<hash>), and repeat that cleanup after a failed or canceled attempt. Do not remove the global node_modules tree because the selected prefix can contain unrelated user-installed packages. This lets a later daemon restart recover from a desktop-close cancellation instead of repeatedly failing with ENOTEMPTY.
  • Validation: Install into a temporary prefix/cache and verify the provider probe, not only npm's exit code. Confirm tutti-agent app-server can start far enough to pass the daemon readiness probe.
  • References: npm_registry.go runtimeprep tutti_agent.go tuttid tuttiagent service.go

Managed npm install fails before reaching every registry

  • Symptom: A Codex or Tutti Agent managed npm install fails immediately with exit code 126. On macOS or another Unix host, stderr contains dirname: command not found followed by a malformed node path. Switching npm registries produces the same failure without meaningful network delay.
  • Quick checks: Inspect the prepared npm path and the install process PATH. Run the managed npm launcher once with that exact environment. If adding /usr/bin:/bin makes it work, the failure is local process setup rather than registry or package availability. On Windows, confirm the structured runner uses cmd.exe /D /S /C call for npm.cmd and retains the inherited System32 path.
  • Root cause: Managed runtime overrides put the bundled Node directory first, but an already-installed runtime fast path can accidentally build the override from an empty base environment. Direct structured execution then exposes the truncated PATH: the Unix npm launcher cannot resolve tools such as dirname, while Windows batch launchers can lose commands supplied by the host environment. A login shell can hide this defect by rebuilding PATH.
  • Fix: Keep structured argv execution and the platform process adapters. When no environment provider is injected, inherit the daemon process environment before prepending managed runtime directories. Do not switch back to shell command strings or hardcode a Unix path into shared installer policy.
  • Validation: Execute a real POSIX npm-style launcher that calls dirname with the production managed-runtime composition, test Windows .cmd argument preservation, build the daemon natively, and cross-compile the Windows agentstatus tests.
  • References: installer_codex_cli.go provider_resolution.go service_helpers.go install_command_windows.go

Tutti Agent unexpectedly loses login after a host auth read failure

  • Symptom: Tutti Agent was previously authenticated, but provider preparation or model discovery changes it to auth_required after the desktop account auth file is temporarily missing, unreadable, or malformed. A token issue rejection may produce the same symptom.
  • Root cause: Provider preparation used to treat one failed observation of the host Account session as a completed logout and removed the durable ~/.tutti-agent/auth.json. The token issue 401 path used the same cleanup helper, even though neither condition proves that the user requested logout.
  • Invariants: Missing, unreadable, malformed, or session-less host auth retains existing Tutti Agent credentials. Failed token issue, validation, provider login, or verification safely restores the previous auth file. Bootstrap and explicit logout resolve a symlinked auth file to the same final target as Tutti Agent, then use its sibling auth.json.refresh.lock. Go flock and Rust fs2 coordinate through the same OS advisory lock on a local filesystem; this is not a distributed lock. Only the completed Account logout callback may delete local provider auth and revoke its refresh token. Logs identify these decisions with event=tutti_agent.auth_bootstrap, action, and reason, without including cookies or tokens.
  • Validation: Run the service/tuttiagent tests covering RetainsAuthWithoutHostSession, RetainsAuthWhenHostAuthIsInvalidJSON, RetainsAuthWhenHostAuthIsUnreadable, RetainsAuthAfterUnauthorizedTokenIssue, and LogoutTuttiAgentUserAuthRemovesAuthAndRevokesToken, plus the reconciliation restoration and refresh-lock serialization tests in the same package.
  • References: service.go tutti-agent-readiness-bootstrap.md

Tutti Agent stays on login while the desktop account is signed in

  • Symptom: The desktop account avatar is present, but Tutti Agent remains auth_required. Repeated bootstrap attempts log stage=login, while the Account service successfully issues and then compensates the short-lived LLM token.
  • Root cause: Desktop Account auth and provider auth are separate. The daemon scopes the provider login subprocess to the canonical user TUTTI_AGENT_HOME. Tutti Agent requires an explicitly configured home to exist during configuration loading, even though its later credential writer can create the default home. Passing a missing explicit directory therefore used to fail before the writer ran.
  • Fix: Create the canonical provider auth home before launching tutti-agent login --with-tutti-llm-tokens. Keep the explicit environment override so session-scoped homes cannot capture durable user credentials. Log auth-home creation without the local path, and include only bounded, access-token- and refresh-token-redacted CLI diagnostics on login failure.
  • Validation: Run the service/tuttiagent tests covering RunTuttiAgentTokenLoginPreparesCanonicalAuthHomeBeforeLaunch, PrepareTuttiAgentAuthHomeRejectsFile, and SanitizeTuttiAgentLoginOutputRedactsTokensAndTruncates. Confirm a clean user home gains .tutti-agent/auth.json, provider status changes from auth_required to ready, and no token value appears in daemon logs.
  • References: service.go tutti-agent-readiness-bootstrap.md

Agent sandbox cannot reach local daemon

  • Symptom: An AgentGUI-backed Codex-compatible turn runs a dynamic Tutti CLI command such as tutti agent get --session-id <id> and gets reasonCode=daemon_unavailable or daemon is not reachable, while the listener file exists and the desktop daemon is serving other requests.
  • Quick checks: Inspect the turn context in the provider session JSONL. If network_access=false, a plain exec_command cannot reach localhost/IPC. Identify the executing provider from that same session instead of inferring it from a queried session ID. For Codex sessions, also confirm the command was not rerun with sandbox_permissions=require_escalated. Other providers need their own local-daemon-capable shell/runtime path, not Codex-specific sandbox syntax.
  • Root cause: Dynamic CLI scopes fetch command capabilities from the local daemon before printing scope help. In a sandboxed provider command environment, localhost access can be blocked even though the daemon is reachable from the host. For a Codex-compatible app-server, omitting sandboxPolicy.networkAccess=true from a readOnly or workspaceWrite turn creates this exact split between successful host requests and failed in-sandbox CLI requests.
  • Fix: Keep the CLI's transport failure message explicit about the sandbox but provider-neutral. The Tutti Desktop host explicitly enables command network access for the built-in codex and tutti-agent app-servers through agentdaemon.Config.CommandNetworkAccessPolicy. Keep this an explicit provider-registry Desktop opt-in instead of branching on provider identity or granting every future app-server network access. This preserves the permission-mode filesystem sandbox and approval policy. Codex should run the CLI normally first and use sandbox_permissions=require_escalated only as a fallback for hosts that do not grant command networking. ACP providers should use an execution environment with localhost/IPC access and not invent Codex flags.
  • Validation: Verify the default adapter policy enables sandboxPolicy.networkAccess=true for Codex and tutti-agent read-only and workspace-write turns. Verify the Desktop host policy rejects Claude Code, external ACP IDs, and empty provider IDs. Retain CLI daemon-client coverage for provider-neutral agent hints and Codex fallback coverage for sandbox_permissions=require_escalated.
  • References: client.go run.go agent daemon runtime.go tuttid command network policy

Codex provider install fails with missing npm

  • Symptom: Agent setup or the onboarding flow repeatedly reports Codex install failures, and tuttid logs show installerKind=codex_cli_latest, exitCode=127, and stderr="zsh:1: command not found: npm" for every npm registry attempt.
  • Quick checks: Search tuttid.log for agent provider install step failed and codex_cli_latest. If each registry fails in milliseconds with exit code 127, stop investigating registry reachability; the command never reached npm networking.
  • Root cause: The Codex CLI installer is daemon-owned but shells out through the daemon environment. Packaged desktop launches may not expose a user-managed npm on PATH, even though Tutti already has a managed Node runtime for workspace app and external-agent npm work.
  • Fix: Resolve user npm first for compatibility, then fall back to the Tutti managed Node runtime's npm before running npm install -g --prefix <stable-user-prefix> @openai/codex --include=optional. Keep the install prefix in a resolver-searched user directory such as ~/.local so the installed codex remains discoverable after install.
  • Validation: Add or run service coverage for a daemon environment with no user npm and a ready managed Node runtime. Then run pnpm lint:go and cd services/tuttid && go test ./service/agentstatus.
  • References: installer_codex_cli.go runtime.go

Codex ACP warns about user-level config as project-local config

  • Symptom: Codex ACP startup logs include Ignored unsupported project-local config keys for user-level Codex config keys such as model_provider, model_providers, or notify.
  • Quick checks: Inspect the session cwd and its parents for an accidental project root, such as a .git directory under $HOME, plus a sibling .codex/config.toml. Inspect the generated codex-home/config.toml; it should be a session-scoped file, not a symlink to the user's global Codex config.
  • Root cause: Codex walks upward from the session cwd to identify the project root. If it reaches a parent directory that also contains .codex/config.toml, Codex can read the user's global config as project-local config, where user-level keys are unsupported.
  • Fix: Codex sidecar preparation must treat CODEX_HOME as the run-scoped user-level Codex home for application-wide injection, not as a project root. Copy the user's config.toml into the run-scoped codex-home, then merge project_root_markers = [] there so ACP sessions do not read accidental parent .codex/config.toml files as project-local config. Do not symlink the config, because the run may need session-specific config that must not mutate the global Codex config. Do not create marker files or directories in the user's cwd.
  • Validation: Add or update runtimeprep tests that verify no cwd marker is created, the generated Codex config preserves user-level provider settings while disabling project root markers, and the user's global config is not modified. Run pnpm lint:go plus cd services/tuttid && go test ./... && go build ./....
  • References: codex.go preparer_test.go

Cursor sessions create project .cursor/skills or AGENTS.md changes

  • Symptom: Starting a Cursor Agent session through Tutti leaves new Tutti-managed skill directories under the repository, such as .cursor/skills/tutti-cli or .cursor/skills/tutti-cli-tutti-6, or appends a BEGIN TUTTI-RUNTIME managed block to the tracked project AGENTS.md. The directories may accumulate across runs and the managed block can appear as a tracked working tree change.
  • Quick checks: Inspect the session sidecar manifest for provider-skill entries pointing inside the workspace cwd. For current sessions, TUTTI_CURSOR_PLUGIN_DIR should point under the session runtime root, for example ~/.tutti-dev/agent/runs/<session>/cursor-plugin/tutti-cli, and the Cursor ACP command should include --plugin-dir <that-dir> before acp. The project AGENTS.md should not receive a TUTTI-RUNTIME managed block for Cursor sessions.
  • Root cause: Cursor supports local plugins via cursor-agent --plugin-dir, but the previous sidecar path reused project-local native skill installation and wrote Tutti injected skills to cwd/.cursor/skills. Repeated runs then allocated suffixed names instead of overwriting the session-owned materialization. The same Cursor preparation path also wrote provider instructions into cwd/AGENTS.md, which dirtied tracked repositories.
  • Fix: Materialize Tutti Cursor skills as a session-scoped Cursor plugin with .cursor-plugin/plugin.json and skills/*/SKILL.md. Generate the canonical runtime policy and its materialized Skill catalog from the same resolved capability profile instead of maintaining a Cursor-specific Skill catalog. Reconcile the session-owned root on every prepare so resume replaces current managed Skills and removes stale managed entries without touching unmanaged directories. Expose the plugin through TUTTI_CURSOR_PLUGIN_DIR, start Cursor ACP as cursor-agent --plugin-dir <plugin-dir> acp. Keep user/project .cursor/skills discoverable for composer options, but never write Tutti injected skills or Tutti runtime instructions into the workspace cwd for Cursor sessions. Cursor ACP does not project plugin Skills or Rules into the model context, so append the prepared policy and dynamic catalog to the first provider-only ACP prompt; never project it as user-visible content. Cursor Agent 2026.07.01-41b2de7 does not load plugin hooks in ACP mode, so do not advertise the dormant background-Task guard in the plugin manifest and do not claim that background Task is blocked. Do not install the hook into user or project Cursor configuration as a workaround.
  • Validation: Add runtimeprep coverage that Cursor prepare creates the runtime plugin and dynamic prompt context while leaving project .cursor/skills and AGENTS.md untouched; add runtime coverage that Cursor ACP includes --plugin-dir and injects the prepared context only on its first provider prompt, and agent service coverage that Cursor composer skill discovery includes plugin skills. Then run cd packages/agent/runtimeprep && go test ./..., cd services/tuttid && go test ./service/agent, and go test ./packages/agent/daemon/runtime.
  • References: cursor.go acp_provider_cursor.go skill_options.go

Cursor ACP Glob/Grep fails for absolute Windows search roots

  • Symptom: A Cursor ACP session on Windows reports that Glob or Grep failed for an absolute path such as C:/Users/.... The underlying provider output may be rg: : IO error for operation on with an empty operand and The system cannot find the path specified, while the same repository is searchable with no explicit path, a relative path, or a native terminal command.
  • Quick checks: Compare the cwd in agent_session.acp.session_new.start with the cwd/protocol_cwd in agent_session.acp.process_start.start. Tutti must pass the native Windows workspace path through unchanged. Run the Cursor version's bundled rg.exe directly against the same absolute path; if it succeeds, the filesystem and Windows rg receiver are healthy. Set TUTTI_ACP_TOOL_DEBUG=1 before starting the daemon when the raw and normalized ACP tool inputs are needed; treat paths in that diagnostic as sensitive.
  • Root cause: Tutti owns the ACP process working directory and the session/new payload, but Cursor owns the built-in Glob/Grep implementation and constructs its internal rg invocation from the provider tool call. In the incident, the provider's error names an empty rg operand even though the requested directory was absolute, so the path was lost inside Cursor after Tutti had already passed the native cwd. The ACP event normalizer only projects the provider's completed call; changing its display fields cannot repair the provider's search request. A separate Tutti defect also used POSIX / as the generic ACP protocol cwd when no cwd was supplied; Windows now falls back to the process's native cwd instead.
  • Fix: Cursor's Windows-only prepared prompt context requires workspace-relative roots for its built-in Glob/Grep tools, uses . for the workspace root, and routes workspace-external or uncertain absolute paths through native rg/PowerShell. If a provider search still fails, the agent must retry via that route and report the retry's actual output; it must not turn the failed call into a successful result.
  • Validation: Run the runtime preparation and standard ACP tests on Windows. With a real Cursor ACP session, search for a known file using (1) a relative workspace root and (2) an absolute path outside the workspace. Verify that the first uses the provider search successfully and the second uses native terminal search with returned file content. Inspect the ACP diagnostic input if the provider still emits an empty rg operand.
  • Limitation: The Cursor provider owns the failing internal path conversion. Tutti's prompt policy prevents the known trigger for model-driven searches, but it cannot guarantee behavior when Cursor ignores that policy. Do not call this provider defect fixed based only on an error card becoming visible; update or report the Cursor runtime if the raw tool input still loses the path.
  • References: standard_acp_session.go, cursor.go, acp_tool_normalizer.go

Cursor read-only mode still creates files without approval

  • Symptom: AgentGUI shows Cursor as Read-only, but the transcript reports a successful Switch Mode followed by Edit, the file appears on disk, and no approval interaction is shown.
  • Quick checks: Inspect the exported session settings and ACP startup logs together. If the durable permissionModeId is read-only while agent_session.acp.permission_mode.start reports mode_id=plan, then the permission tier was mapped to Cursor's planning workflow rather than its read-only execution mode. Confirm the turn contains no session/request_permission before investigating AgentGUI approval cards.
  • Root cause: Cursor plan and ask are different workflow modes. Plan can transition into implementation, including a provider-owned Switch Mode, whereas Ask retains read/search tools without making changes. Mapping Tutti's durable read-only permission tier to Plan therefore delegated a security boundary to a workflow that could advance into editing. A provider-owned tool call is historical activity, not a canonical approval Interaction, so AgentGUI correctly had no approval request to render.
  • Fix: Map Cursor read-only to ACP ask. Keep the independent AgentGUI plan-mode control mapped to ACP plan, and when plan mode is turned off, restore the runtime mode derived from the durable permission tier instead of assuming agent.
  • Validation: Cover startup tier mapping (read-only -> ask) and a read-only plan-mode round trip (ask -> plan -> ask). With a real Cursor ACP binary, verify Ask can search and read project files, a request to create a file does not write one, and no provider-owned mode switch silently enables Edit.
  • References: providers.go acp_provider_cursor.go standard_acp_adapter_test.go

Cursor CLI can ask questions but Cursor ACP cannot

  • Symptom: cursor-agent in its interactive CLI can pause and ask the user a structured question, while the same prompt through cursor-agent acp either continues without asking or reports that no question tool is available.
  • Quick checks: Probe the raw ACP initialize and session/new exchange before changing Tutti's interaction projection. Changing ACP clientInfo, advertising extra client capabilities, or passing a root -H x-cursor-client-type: cli header does not add the tool. If an injected HTTP MCP exposes AskUserQuestion, confirm Cursor emits session/request_permission with kind other, then a Tutti question interaction whose metadata reports providerMethod=mcp/tools/call.
  • Root cause: Cursor's ACP transport identifies itself internally as client type acp and currently omits the interactive CLI's built-in question tool. The ACP bridge contains a native cursor/ask_question request path, but changing initialize metadata does not enable it; the transport middleware also overwrites a caller-supplied client-type header.
  • Fix: Preserve the native cursor/ask_question handler for Cursor versions that expose it. For affected versions, bind Tutti's AskUserQuestion through the session-scoped HTTP MCP capability advertised by ACP. Keep the bridge on a loopback listener with a per-session bearer token, activate it only for the exact running turn, and route the blocking call through the existing Tutti interactive state machine. Auto-approve only Cursor's exact non-mutating permission titles for this Tutti-owned tool; all other tool permissions must continue through the configured permission tier.
  • Validation: First assert session/new contains the HTTP MCP binding and that a real HTTP tools/call blocks until SubmitInteractive supplies structured answers. Keep native question and normal permission-tier regression coverage. Finally, run a real Cursor ACP turn: require the model to call AskUserQuestion, answer the emitted Tutti interaction, and verify the provider turn completes. On Windows, this also verifies the loopback listener and local process transport; retain POSIX coverage for the same platform-neutral socket path.
  • References: cursor_acp_question_mcp.go cursor_acp_interactive.go standard_acp_session.go

Codex is installed but Tutti keeps asking to connect or install it

  • Symptom: Tutti shows the Codex setup surface even though the Codex executable exists. Provider diagnostics report no_ready_candidate, and an install action may start repairing the existing npm package.
  • Quick checks: Correlate agent_session.acp.request.sent method=initialize with a later agent_session.acp.response.unmatched for the same request ID. A response arriving after the former three- or five-second probe window proves a slow cold start rather than a missing CLI.
  • Root cause: Codex app-server startup may take longer than the generic fast health-probe budget. Runtime and authentication probes could expire before a valid initialize response, causing the discovered executable to be classified as unavailable and routed into installation.
  • Fix: Give only Codex runtime and authentication probes a ten-second cold-start budget. Other providers retain their existing bounds, and a faster Codex response still returns immediately.
  • Validation: Cover the Codex-specific timeout policy, its descriptor-projected auth timeout, and preservation of a longer explicit test/operator timeout. Run cd services/tuttid && go test ./service/agentstatus.

Codex provider appears logged in with an empty auth.json

  • Symptom: Provider management reports Codex as logged in when ~/.codex/auth.json is {}, but starting Codex fails during TUI bootstrap with account/read failed: plan type is required for chatgpt authentication.
  • Quick checks: Compare codex login status with a direct app-server account/read. The former may print Logged in using ChatGPT and exit successfully solely because auth.json exists, while the latter rejects the incomplete account.
  • Root cause: Codex provider status used the generic text-command runner and accepted codex login status as authoritative. That command's file-level check is weaker than the account validation performed by the TUI and app-server.
  • Fix: Use the descriptor-owned codex_app_server_account auth runner. It performs the formal app-server initialize handshake and calls account/read; only a structurally valid returned account is authenticated. A failed or malformed response remains unknown and never falls back to auth-file existence.
  • Validation: Cover authenticated, login-required, and account/read-error responses in the shared app-server probe, then verify tuttID does not authenticate from a present marker when account/read is unknown.

Codex provider shows login required when global service tier is legacy

  • Symptom: The workspace dock popup shows Codex as needing login even though ~/.codex/auth.json contains OAuth tokens.
  • Quick checks: Run codex login status. If it prints Error loading configuration: ... unknown variant ... expected fast or flex, inspect the top-level service_tier in ~/.codex/config.toml.
  • Root cause: Newer Codex CLIs only accept service_tier = "fast" or "flex" in global config. Older values such as "default" or "priority" make the status command fail before it can report auth state, so tuttid classifies auth as unknown and the renderer shows login/refresh.
  • Fix: Provider status and login commands should pass a temporary Codex config override such as -c 'service_tier="fast"' instead of mutating the user's global config. Session-scoped Codex homes should continue sanitizing copied config through codexConfigWithSupportedServiceTier.
  • Validation: Add or update agentstatus tests for the Codex status/login command shape, then run cd services/tuttid && go test ./service/agentstatus.

Codex provider shows login required when only an API key is configured

  • Symptom: The environment wizard / dock marks Codex as needing login ("未登录") even though an API key is configured and Codex sessions can already run successfully. Common sources are OPENAI_API_KEY in the environment, api_key in ~/.codex/config.toml, or OPENAI_API_KEY inside ~/.codex/auth.json as written by custom-provider switchers.
  • Quick checks: Run codex login status (often prints Not logged in). Confirm an API credential exists via echo $OPENAI_API_KEY, grep -E 'api_key' ~/.codex/config.toml, or a non-empty OPENAI_API_KEY field in ~/.codex/auth.json.
  • Root cause: codex login status only reflects a ChatGPT OAuth session. API-key billing from the environment, config, or auth.json is invisible to that command, so tuttid used to treat the provider as auth_required and block the wizard even though the runtime can authenticate with the key.
  • Fix: Provider status should call providerHasAPICredential for Codex the same way it does for Claude Code, including auth.json OPENAI_API_KEY. When an API key is present, report auth as authenticated with method apiKey / label API Usage Billing instead of requiring login. A bare custom base URL without a credential must not trigger this override.
  • Validation: Add or update agentstatus tests for environment, config.toml, and auth.json API-key-without-login readiness, then run cd services/tuttid && go test ./service/agentstatus.

Codex session fails with not connected when config file dependencies are relative

  • Symptom: Codex is installed and codex login status reports logged in, but Tutti chat fails with agent session is not connected. Daemon logs show thread/start failing with failed to load configuration: No such file or directory (os error 2). Tools such as CC Switch often set model_catalog_json = "cc-switch-model-catalog.json" in ~/.codex/config.toml. User-managed instruction files can reproduce the same failure with model_instructions_file = "gpt5.5-unrestricted.md". If that catalog file is missing entirely, the same config error can also make provider status show login required (auth_unknown) even though OAuth tokens exist.
  • Quick checks: grep -E 'model_catalog_json|model_instructions_file' ~/.codex/config.toml and confirm each relative referenced file exists under ~/.codex/. Inspect the run-scoped ~/.tutti-dev/agent/runs/<session>/codex-home/ (or ~/.tutti/... in prod): config.toml is copied, but relative catalog and instruction files must also be present there.
  • Root cause: Tutti prepares a run-scoped CODEX_HOME and copies only config.toml (plus auth/plugin/skill exposure). Relative model_catalog_json and model_instructions_file paths resolve against that sandbox home, so the dependency is missing unless Tutti mirrors it.
  • Fix: After copying config.toml, resolve top-level model_catalog_json and model_instructions_file. For relative paths under ~/.codex, symlink (or copy) the file into the run-scoped CODEX_HOME at the same relative path. Absolute paths need no mirror but must be validated in place. Missing, unreadable, non-regular, or illegal dependencies should fail preparation before provider startup with a safe agent.config_dependency_unavailable diagnostic. Do not mutate the user's global config.
  • Validation: Add or update runtimeprep tests that set relative catalog and instruction files beside config.toml and assert the sandbox exposes them. Run cd packages/agent/runtimeprep && go test ./....
  • References: codex.go preparer_test.go

Codex model picker collapses to the configured model

  • Symptom: With the default OpenAI provider, the composer model picker contains only the top-level model from ~/.codex/config.toml, while a directly initialized codex app-server connection returns multiple models from model/list.
  • Quick checks: Compare the composer options with a directly initialized Codex app-server model/list response. The returned catalog remains authoritative when model_provider is custom; model_catalog_json changes the catalog Codex returns but is not required to prevent Tutti from trimming it. Search daemon logs for composer model catalog lookup failed, Not initialized, or a Codex model/list timeout, then compare the request sequence with the app-server initialization contract.
  • Root cause: Model discovery sent initialize and model/list back to back without reading the initialize response or sending the initialized notification. An app-server that enforces the connection handshake can reject or withhold model/list. The failed catalog then falls back to the configured model, making the protocol failure look like a valid one-option picker.
  • Fix: Keep one stdout scanner for the exchange: send initialize, read and validate the matching response, send initialized, and only then send model/list. Preserve the configured-model fallback for genuine discovery failures.
  • Validation: Use a fake app-server that rejects model/list until it has returned the initialize response and received initialized. Run cd services/tuttid && go test ./service/agent -run TestCodexCLIModelLister plus pnpm lint:go.
  • References: codex_model_catalog.go codex_model_catalog_test.go

Codex composer model and reasoning selectors stay loading

  • Symptom: The empty Codex composer shows loading placeholders for both model and reasoning, even though provider status reports Codex as ready.
  • Quick checks: Correlate a ready Codex provider snapshot in tuttid.log with agent.composer_options.load in tutti-desktop.log. A duration near 15 seconds with errorCode=ETIMEDOUT means the Desktop request deadline expired before Composer Options returned. If tuttid later logs superfluous response.WriteHeader, or a detached codex app-server remains after the request, the canceled handler did not finish cleaning up its discovery subprocess.
  • Root cause: Codex Composer Options has two independent waits: model/list feeds the model, reasoning, and speed controls, while app-server capability discovery feeds skills and capability entries. A legacy combined response can still wait for both, and repeated capability failures can otherwise start another eight-second probe for every refresh.
  • Fix: Desktop requests section=core for model controls. It requests section=capabilities only when the user opens or uses a capability surface, so the capability response and its eight-second provider timeout never block the model controls. The legacy section=full response still starts both catalogs concurrently. Capability loads use single-flight sharing plus a short negative cache so identical callers do not stampede a broken app-server. Run every short-lived Codex app-server in its own process group, begin process reaping immediately, and make timeout cancel the entire group. The daemon keeps one initialized Codex app-server session warm per provider for up to two minutes, and refreshes the five-minute model catalog in the background after expiry or an auth/config invalidation. Identical atomic rewrites of Codex auth/config do not invalidate the catalog because the watcher compares file content. Keep the Desktop deadline unchanged so a genuinely stuck daemon request still fails closed.
  • Validation: Block both catalog fixtures and assert both start before either is released. Use a fake app-server whose child retains stdout and assert model and capability timeouts return promptly with no surviving child. Assert concurrent cold catalog callers share one fetch, stale options remain visible while the background refresh runs, and repeated requests reuse one app-server process. Finally, time a cold Composer Options request and confirm it completes within the Desktop deadline. Useful logs are agent.model_catalog.fetch_start, stage_settled, fetch_settled, request_settled, agent.composer_options.load, and process_idle_close. Composer telemetry reports section/stage, outcome, duration, and bounded model identifiers without paths or settings. When an auth/config watcher causes invalidation, agent.model_catalog.invalidated also includes the exact changed file and change kind; use that field to distinguish Codex auth/config churn from a provider-side fetch failure.
  • References: composer_options.go codex_appserver_process.go codex_model_catalog.go codex_capability_catalog.go

Codex custom model_provider hides app-server models, duplicates replies, or shows metadata warnings

  • Symptom: With model_provider set to a custom endpoint and model set to a vendor model id, the composer may show only the configured model even though Codex model/list returns several models and their reasoning efforts. A turn may also show the same assistant reply twice, or the transcript may repeatedly display Model metadata for ... not found. Defaulting to fallback metadata.
  • Quick checks: Inspect top-level model_provider, model, and model_catalog_json in ~/.codex/config.toml, then compare Tutti's composer options with a direct app-server model/list response. In persisted session messages, look for two completed assistant rows with equivalent text but different message ids in one turn. The composer should preserve the models and supportedReasoningEfforts returned by app-server regardless of whether the active model_provider is OpenAI or custom.
  • Root cause: A Tutti post-processing rule treated any non-default model_provider as proof that Codex's discovered catalog was unrelated and collapsed it to the top-level configured model. This also discarded per-model reasoning metadata. Separately, Codex can finalize an assistant item after an early stream boundary and replay the answer again in turn/completed, sometimes with whitespace polish; treating each report as a new segment creates duplicate bubbles. The model-metadata warning is runtime diagnostic noise rather than an actionable user error. Persisted skill-context warnings may omit their optional source metadata, and Codex has emitted both percentage and non-percentage variants of that wording.
  • Fix: Treat Codex app-server model/list as the authoritative catalog regardless of model_provider. Preserve the full returned list and reasoning metadata; use the top-level configured model only to select the default or as the existing fallback when discovery fails. Preserve the assistant message id for whitespace-equivalent item-finalization text and ignore turn-final text after an assistant segment has already completed. Filter the metadata fallback warning through the same AgentGUI diagnostic-notice projection used for skills-context-budget warnings. Match the optional percentage in the skills warning as diagnostic context rather than as part of its identity; accept a missing source only for that exact warning, preserve explicitly non-runtime notices, and keep the metadata fallback warning runtime-only.
  • Validation: Run go test ./packages/agent/daemon/runtime -run 'TestApplyAssistantFinalText|TestApplyAssistantTurnFinalText|TestCodexAppServerAdapterExecStreamsTurn', cd services/tuttid && go test ./service/agent -run TestAgentModelCatalog, and the focused AgentGUI projection test.

Claude SDK Grep or Glob unavailable despite Claude Code preset

  • Symptom: Claude emits Grep or Glob, but the SDK returns No such tool available and suggests using shell grep or find.
  • Root cause: Some Claude Code SDK native builds expose search through Bash by default. The claude_code tool preset may not register dedicated Grep/Glob tools unless the host also lists them in allowedTools or tools.
  • Fix: Keep the claude_code preset as the base tool set, and explicitly include Grep and Glob in Claude SDK allowedTools. Avoid replacing tools with a short string list unless the host intentionally wants to narrow every built-in tool available to Claude.
  • Validation: Assert the sidecar start payload carries allowedTools: ["Grep", "Glob"], and typecheck against the local @anthropic-ai/claude-agent-sdk definitions.

Provider process loses final stdout or a sidecar fails during startup

  • Symptom: A short-lived provider helper exits successfully but its final stdout frame is absent, or a long-lived SDK sidecar intermittently appears to exit before its startup response is consumed.
  • Root cause: os/exec.Cmd.StdoutPipe and StderrPipe make pipe draining and Cmd.Wait ordering the caller's responsibility. Waiting for readers before Cmd.Wait makes process reaping depend on pipe EOF, while calling Cmd.Wait first can close a short-lived process's pipes before its last bytes are delivered.
  • Fix: Give Cmd.Stdout and Cmd.Stderr frame writers instead of managing StdoutPipe/StderrPipe directly. os/exec then owns the copy goroutines and Cmd.Wait returns only after the final writes complete, without delaying startup-time streaming for a live sidecar.
  • Validation: Run the local-process transport tests together with the Claude SDK sidecar start, approval, and controller tests repeatedly. Keep explicit assertions that final stdout arrives before the exit frame and that a live process can exchange frames before it exits.

Concurrent agent CLI installs corrupt shared npm global state

  • Symptom: Two agent-provider installs started close together can leave global npm bins or package directories half-written. Follow-up probes may report cli_not_found, acp_adapter_not_found, or a binary that exists but fails immediately after install.
  • Quick checks: Confirm whether more than one tuttid agent-provider install action or desktop install button fired at roughly the same time for commands shaped like npm install -g .... Inspect the daemon run-state lock path under TUTTI_STATE_DIR/run/locks/npm-global-install.lock while an install is in progress to verify later installs are waiting instead of running in parallel.
  • Root cause: npm global installs mutate shared package and bin locations. Without a daemon-owned cross-process lock, concurrent npm install -g commands can race while writing the same global state and leave a corrupted runtime.
  • Fix: Serialize agent-provider npm install -g commands behind the daemon install lock and keep the lock path under daemon-owned state. Start the install timeout only after the lock is acquired so queued installs do not consume their npm execution budget while waiting. Do not auto-delete the lock on a timer. Instead, recover the lock during daemon startup only when the recorded owner pid is no longer running. If recovery is still needed manually, clear npm-global-install.lock only after verifying no install is still running.
  • Validation: Run pnpm lint:go plus cd services/tuttid && go test ./... && go build ./.... Then trigger two install actions in quick succession and confirm the second waits for the first instead of starting another global npm mutation.

Agent provider install looks idle while a non-Codex installer is running

  • Symptom: Provider setup appears stuck or idle even though tuttid.log has an agent provider install step started entry and no matching completed/failed line yet. This is most visible for Claude Code CLI or ACP adapter installs.
  • Quick checks: Compare the install start timestamp with the log export timestamp before calling it hung. Also check for a later completed install log line and the provider binary path in the rechecked runtime log. If tuttid.log shows active_action.output_appended but desktop diagnostics keep reporting logLines=0, check whether the status request copied activeAction before installer output arrived, or whether the renderer stopped refreshing while the install action was still pending.
  • Root cause: The provider installer is daemon-owned and can legitimately run for minutes, but renderer progress must come from the generic provider activeAction status field. Do not special-case long-running install progress to Codex.
  • Fix: Set, stream stdout into, expose, and clear ActiveAction for every provider install action. Keep provider-specific installer details inside services/tuttid/service/agentstatus and project only the transport-safe active action shape through the API seam. Refresh the provider's active action snapshot at the end of List, and short-poll provider status while a daemon install action is pending so live installer output can reach the wizard.
  • Validation: Run cd services/tuttid && go test ./service/agentstatus ./api and pnpm check:api-generated. Trigger a Claude Code install and confirm status responses include activeAction while the CLI or adapter step is in flight.

Legacy Claude ACP adapter appears stale after external registry migration

  • Symptom: With TUTTI_CLAUDE_CODE_RUNTIME=acp, Claude Agent provider status is not ready, or live ACP options do not match the package version advertised by the ACP External Agent Registry. Another form is Claude Code context usage briefly showing 0% during a running session or around compaction, then returning to the prior nonzero value on the next usage update. A third form is new Claude Code sessions failing during startup with Invalid value for config option fast: standard.
  • Quick checks: First confirm the runtime is legacy ACP. The default Claude Code runtime is SDK; SDK provider availability checks the claude CLI plus the Claude SDK sidecar entry and must not require claude-acp. Inspect <state-dir>/agent-providers/external-agent-registry/cache/registry.json and the package manifest under <state-dir>/agent-providers/external-agent-registry/packages/claude-acp/node_modules/@agentclientprotocol/claude-agent-acp/package.json. which claude-agent-acp only describes a user/global shim and is no longer the Tutti-owned Claude adapter source. For usage flicker, inspect that package's dist/acp-agent.js for sessionUpdate: "usage_update" near compact_boundary; it must not publish used: 0 when the SDK getContextUsage() probe fails. For speed failures, inspect the live fast config option values advertised by the managed package; supported native Claude ACP packages that fall back to select options use off and on.
  • Root cause: Tutti resolves Claude ACP from the external agent registry and installs the npm adapter into a daemon-owned prefix with managed npm. A stale or missing prefix package, stale registry cache, or unavailable managed Node runtime can make the adapter unavailable even when a global claude-agent-acp exists. Usage flicker can also come from the managed bridge bundle itself publishing an invalid zero context usage after a failed compact-boundary usage probe; AgentGUI only displays the normalized runtime context it receives. Speed failures come from treating Tutti's internal standard / fast speed tier values as ACP wire values; supported Claude ACP packages advertise native fast config values as off / on.
  • Fix: Run the provider install action so tuttid refreshes the registry, resolves the managed Node runtime, and installs the npm package into the per-agent prefix. Do not compensate by changing static model catalogs for behavior that should come from the live ACP package. Keep the Tutti claude-agent-acp patch script authoritative for bridge behavior and apply it to the managed package; do not mask invalid usage in AgentGUI. Keep Tutti's internal speed tiers stable, but translate Claude ACP fast config values at the adapter boundary according to the live advertised options, and normalize the live value back before projecting runtime settings.
  • Validation: Run go test ./services/tuttid/service/agentstatus, then confirm a stale global adapter is ignored and the install action uses managed npm with --prefix <state-dir>/agent-providers/external-agent-registry/packages/claude-acp. For usage flicker, run node services/tuttid/service/agentstatus/assets/patch-claude-agent-acp.mjs --dist <managed-acp-dist> twice and confirm the second run reports no changes, then inspect the bundle and confirm lastAssistantTotalUsage = usedTokens ?? 0 is absent. For speed compatibility, run the Claude ACP adapter tests that cover native off / on advertised values and confirm legacy standard / fast advertised values are ignored.
  • References: service.go store.go patch-claude-agent-acp.mjs

Cursor ACP context ring stays empty or usage looks wrong

  • Symptom: A Cursor AgentGUI session shows an empty context ring, 0%, or stale context usage while the session is actively running. Check & Settings may show the Cursor subscription tier from cursor-agent about, but account quota still reads as unsupported.
  • Quick checks: Grep tuttid logs for event=agent_session.acp.usage_update while reproducing the session. Inspect provider, parsed_ok, context_known, raw_used, raw_size, used_tokens, total_tokens, and quota_count on each event. If no events appear, Cursor is not pushing ACP usage_update for that session. If events appear with parsed_ok=false or missing raw_used / raw_size, inspect the raw ACP payload shape before changing AgentGUI.
  • Root cause: Tutti's standard ACP adapter already normalizes usage_update into runtime context, but Cursor may omit the event or publish a different payload than Codex/Claude bridges. Subscription tier display comes from auth probing, not from usage_update.
  • Fix: Use the diagnostic log fields to decide whether to fix adapter parsing or wait for Cursor to publish usage updates. Do not mask missing usage in AgentGUI when the provider never sent usage_update.
  • Validation: Run go test ./packages/agent/daemon/runtime -run UsageUpdate and start a Cursor session while tailing tuttid logs for agent_session.acp.usage_update.
  • References: standard_acp_adapter.go acp_live_state.go service_helpers.go

Claude SDK model aliases resolve to configured Anthropic defaults

  • Symptom: A Claude Code SDK session shows a Tutti composer model alias such as sonnet or haiku, but the model response or error mentions a different concrete model such as mimo-v2.5-pro.
  • Quick checks: Inspect the effective Claude Code settings env from $CLAUDE_CONFIG_DIR/settings.json or ~/.claude/settings.json, especially ANTHROPIC_MODEL, ANTHROPIC_DEFAULT_SONNET_MODEL, ANTHROPIC_DEFAULT_HAIKU_MODEL, ANTHROPIC_DEFAULT_OPUS_MODEL, and ANTHROPIC_BASE_URL. Also inspect the session runtime_context_json for providerConfig.baseUrl and model before assuming the UI selected the concrete provider model directly.
  • Root cause: The SDK sidecar passes Tutti's Claude Code aliases to @anthropic-ai/claude-agent-sdk, while the SDK/Claude Code runtime still resolves those aliases through the user's Claude settings env. A proxy such as MiMo may map sonnet to a configured concrete model, and provider access errors then mention that concrete model instead of the Tutti alias.
  • Fix: Keep the sidecar inheriting the user's Claude settings so credentials and base URL keep working. Fix provider access by changing the user's Claude settings or managed provider model config, not by hard-coding Tutti's static alias list. Report a typed SessionStateSnapshot.Capabilities value before projecting the session to AgentGUI so stale provider-private runtime context cannot disable prompt-image paste.
  • Validation: Confirm the session runtime context shows adapter: claude-agent-sdk, the expected providerConfig.baseUrl, and confirm the typed session capability snapshot includes imageInput. Then run the Claude SDK adapter tests plus the agent service capability-projection tests.
  • References: claude_sdk_adapter.go service_helpers.go composer_live_model_discovery.go

Claude SDK rejects live bypassPermissions mode

  • Symptom: A Claude Code SDK session starts in default, auto, or plan mode, then live switching to bypassPermissions fails with Cannot set permission mode to bypassPermissions because the session was not launched with --dangerously-skip-permissions. Or the session state already shows permissionModeId=bypassPermissions, but ordinary tools such as Bash still surface AgentGUI approval prompts.
  • Quick checks: Inspect the SDK query options emitted by packages/agent/claude-sdk-sidecar. allowDangerouslySkipPermissions must be enabled when the query is created, not only when the initial permission mode is already bypassPermissions. In root/sandboxed runtimes, confirm the sidecar process receives IS_SANDBOX=1. If the query launched correctly, inspect the sidecar canUseTool callback path; bypass mode should short-circuit ordinary tools after preserving special handling for AskUserQuestion and ExitPlanMode.
  • Root cause: Claude SDK treats bypass permission support as a session launch capability. query.setPermissionMode("bypassPermissions") cannot enable that capability after the query has already started. Tutti's sidecar also owns the canUseTool callback; if that callback always requests AgentGUI approval, it can reintroduce prompts even after the SDK permission mode is bypass.
  • Fix: Gate bypass availability with the same rule as Claude Agent ACP: non-root processes can bypass, and root processes can bypass only when IS_SANDBOX is set. Launch the SDK query with allowDangerouslySkipPermissions whenever that gate passes, regardless of the current permission mode. In canUseTool, handle AskUserQuestion and ExitPlanMode first, then directly allow ordinary tools when the effective permission mode is bypassPermissions.
  • Validation: Add sidecar coverage for a default session whose query still receives allowDangerouslySkipPermissions: true, plus daemon runtime coverage that Claude SDK sidecar process env includes IS_SANDBOX=1. Add callback coverage proving bypass mode allows an ordinary Bash request without approval_requested, while AskUserQuestion still surfaces user input.

Extension session create rejects a semantic permission id

  • Symptom: An extension exposes a full-access option whose id is provider-specific, such as bypassPermissions, but session creation fails after a caller sends full-access. The Tutti CLI reports reasonCode=unsupported_permission_mode_id and lists the currently accepted ids.
  • Quick checks: Query Composer Options for the exact Agent target. Compare permissionConfig.modes[].id with permissionConfig.modes[].semantic and inspect the submitted permissionModeId. Multiple ids may legitimately have the same semantic.
  • Root cause: semantic is provider-neutral classification metadata. It is only a launch id for extensions whose signed Composer profile declares semantic launch permission mapping. Runtime-id extensions require the exact provider-owned id returned by Composer Options.
  • Fix: Refresh Composer Options and round-trip the selected id verbatim. Do not hard-code a provider permission alias or collapse modes by semantic. Extension implementations keep the signed Composer profile as the launch contract; runtime config options may enrich current state and labels but cannot rewrite ids. Composer Options ignores a persisted default that is no longer in the signed profile and falls back to live runtime state, then the profile default; an explicit obsolete id still fails. Exact runtime ids also take precedence over semantic and historical aliases in the Standard ACP lookup.
  • Validation: Confirm Composer Options preserves every declared runtime id in profile order, including multiple ids with the same semantic, and that the exact selected id reaches the runtime start input and final Standard ACP mapping. Test an exact runtime id that collides with another mode's semantic alias, plus an obsolete persisted default. An invalid explicitly supplied semantic alias must fail before hidden discovery or visible session creation.
  • References: extension_composer_options.go composer_runtime_context.go

Claude Code logs out after sending a message (invalid_grant, credentials wiped)

  • Symptom: Inside the desktop app, sending a message around the OAuth token expiry window leaves Claude Code in a "Not logged in · Please run /login" state. The keychain entry (Claude Code-credentials) has empty accessToken/refreshToken and expiresAt: 0, while the plaintext ~/.claude/.credentials.json may still hold a valid token. The Claude CLI alone does not reproduce it.
  • Quick checks: Capture /v1/oauth/token traffic (mitmproxy). A failure may show Client disconnected immediately before later refresh attempts return 400 invalid_grant. Search tuttid.log for event=agent.model_discovery.hidden_session_triggered provider=claude-code; this info-level event confirms that Tutti actually triggered a hidden Claude live-model discovery session. The event intentionally includes only the provider and random discovery-session id.
  • Root cause: Composer-options loading spawned a hidden, visible:false Claude live-model discovery session that shares the on-disk credential store with the real conversation session and deleted it as soon as the model list was read. When it performs an OAuth refresh near expiry, the server can rotate the refresh token even if the local client disconnects before receiving or persisting the response. The real session later refreshes with the now-consumed refresh token, gets 400 invalid_grant, and Claude Code wipes the stored credentials. Because fallbackStorage prefers the (now empty) keychain entry over the still-valid plaintext file, the user is locked out.
  • Fix: Cold composer options must always have a static Claude fallback (default, opus, sonnet, haiku, plus any configured custom model) so the UI never depends on live discovery. A cold-start live discovery may run at most once per provider/workspace/cwd cache key, but it must be hidden, serialized with other Claude startups, and deleted only after a delayed grace period rather than immediately after the model list appears. Successful discovery updates the daemon live-model cache; later composer-options calls prefer cached models or model options reported by a real running Claude session over the static fallback. Claude Create model validation should only use cached live-model options; it must not start discovery. If the daemon exits before the delayed cleanup timer fires, later persisted-session reads must delete the stale hidden discovery session instead of restoring it as a real conversation.
  • Validation: Add daemon service tests for Create cache-only validation, SendInput waiting on the Claude startup slot before runtime exec, static Claude cold-start model options, reusing model options from a running Claude session, cold-start discovery running once, delayed hidden discovery cleanup, and stale persisted hidden discovery cleanup after restart. Run targeted agent service Go tests plus the daemon Go lint/test/build lanes.
  • References: composer_live_model_discovery.go model_validation.go
  • Symptom: A Claude Code turn renders the plain text Not logged in · Please run /login. Clicking /login tries to open a local file and may show a missing-file toast instead of the Agent login flow.
  • Quick checks: Inspect the durable assistant message and owning Turn. If both are completed even though the body is the standalone login notice, the Claude SDK returned an authentication failure as successful assistant output. The generic failed-message recovery path will not run for that shape.
  • Root cause: The Claude SDK can pair its standalone login notice with a successful result. AgentGUI previously recovered plain authentication errors only when the message status was failed, so the completed notice fell through to Markdown rendering, where /login also matched the local absolute-path link rule.
  • Fix: Preserve provider-agnostic recovery for failed messages. Additionally, for completed messages, recognize the short, whole-message standalone login notice and recover it as auth_required. Match the provider-owned output shape rather than branching on provider identity; keep the matcher length-bounded and anchored so ordinary answers that discuss login text are not reclassified.
  • Validation: Cover the completed Claude notice rendering the authentication card and a normal completed answer that quotes the notice remaining ordinary content.
  • References: agentErrorPresentation.ts AgentMessageBlock.tsx

Model Plan check succeeds but Kimi Claude Code turns wait and then return 401

  • Symptom: An Anthropic-protocol Model Plan using https://api.kimi.com/coding/ passes model discovery/inference checks, but a custom Agent backed by Claude Code stays running for roughly three minutes and then appends Failed to authenticate. API Error: 401.
  • Quick checks: Confirm the submit claim is accepted, the user message exists, and the runtime reached runtime.turn_goroutine_started. Inspect only environment variable names and whether each is non-empty, never credential values. If the child has a non-empty ANTHROPIC_AUTH_TOKEN but no non-empty ANTHROPIC_API_KEY, the plan credential was injected with the wrong auth shape.
  • Root cause: Model Plan detection sends Anthropic requests with x-api-key. Runtime preparation previously treated every non-api.anthropic.com endpoint as a bearer-token relay and launched Claude Code with ANTHROPIC_AUTH_TOKEN. Kimi Coding's Claude Code contract requires ANTHROPIC_API_KEY, so the same valid credential passed detection and failed only in the real Agent process.
  • Fix: Keep the existing bearer default for relay endpoints, but classify api.kimi.com alongside the official Anthropic endpoint for ANTHROPIC_API_KEY injection. Also preserve Claude SDK assistant.error and result.is_error as failed message/turn state; a result subtype of success is not authoritative when either error signal is present.
  • Validation: Run go test ./packages/agent/runtimeprep ./packages/agent/daemon/runtime and pnpm --dir packages/agent/claude-sdk-sidecar test. Verify a newly created Kimi-backed session has a non-empty ANTHROPIC_API_KEY, an empty ANTHROPIC_AUTH_TOKEN, and no 401. Existing running sessions retain their launch environment and must be recreated.
  • References: Kimi Claude Code setup model_endpoint.go messageRouter.ts

Kimi Code shows no models and silently ends every turn

  • Symptom: The Kimi Code model picker is empty. Sending a prompt appends the user message, but no assistant message or visible error appears and the Turn settles immediately.
  • Quick checks: Inspect the ACP session/new result. Kimi may advertise the model selector through configOptions[id="model"] with an empty currentValue and empty options, rather than through the top-level models object. If session/prompt then returns stopReason: "end_turn" without an assistant chunk or tool call, treat it as a hidden provider failure rather than a successful empty answer. Check that the signed Kimi Extension routes /status to the native status panel with showStatus and /usage to the runtime with submitImmediate.
  • Root cause: Kimi Code can create an ACP session while no model is configured. Its ACP adapter maps some underlying model, authentication, plan, and balance failures to a normal end_turn with no output because ACP has no failed stop reason. The setup guard previously recognized only the top-level models shape.
  • Fix: Reject both empty ACP model shapes during generic setup. A normal ACP terminal with neither assistant output nor tool activity must settle as provider_empty_response, producing a visible conversation error card that points users back to model and account setup. Turns with only thinking or a system notice remain valid because they produced observable assistant output. Keep Kimi's slash-command behavior declarative in the signed Extension.
  • Validation: Cover empty and populated models/configOptions selectors, thinking-only and notice-only ACP turns, and an otherwise normal empty ACP end_turn. Validate in the Extension repository that /status uses showStatus and /usage uses submitImmediate against the pinned real runtime.
  • References: standard_acp_setup.go standard_acp_turn.go createDesktopAgentStatusSource.ts Agent Extensions Kimi Code Agent Extension

CodeBuddy account panel does not distinguish API billing from Coding Plan

  • Symptom: CodeBuddy /status opens the native account panel, but its limits row says the current Agent does not provide quota limits. Ordinary API keys and Coding Plan credentials also have the same presentation.
  • Quick checks: Confirm the provider is acp:codebuddy. Inspect CodeBuddy's effective CODEBUDDY_API_KEY, CODEBUDDY_BASE_URL, CODEBUDDY_AUTH_TOKEN, and apiKeyHelper presence without copying credential values into logs. Coding Plan keys use the sk-sp- prefix or an endpoint whose path contains coding.
  • Root cause: CodeBuddy's ACP usage_update reports context usage and per-request cost, but the pinned runtime does not expose or document a complete account-level credit-balance contract. Private product endpoints, filtered package queries, and a single page of resource rows cannot prove a complete account balance. Tutti previously had no CodeBuddy account probe, so the native panel treated the provider as unsupported and could not identify the billing mode.
  • Fix: CodeBuddy's Extension declares a separately published Provider-owned account usage companion. The companion owns only CodeBuddy's effective configuration precedence and billing-mode classification. Tutti executes the generic accountUsage capability and consumes a closed Provider-neutral snapshot. Ordinary API keys report api with quota state not_applicable; Coding Plan reports coding_plan with quota state unavailable; native authentication reports provider_account with quota state unavailable. The UI renders API not_applicable as —; Coding Plan and native accounts render the localized account-quota-unavailable copy. Neither presentation claims a verified credit balance. Until the Provider publishes a contract that proves a complete snapshot, the companion emits no exact Credits. It does not enumerate or read native session files, decode JWTs, execute credential helpers, inspect local expiresAt, or call private account endpoints. Runtime/ACP therefore remains the only login and refresh authority. Tokens, account identifiers, paths, raw Provider responses, and Provider messages never cross the daemon API or renderer IPC and never enter logs.
  • Validation: Cover ordinary API keys, Coding Plan keys, authentication tokens, apiKeyHelper, effective settings precedence, and stable errors. Prove that simultaneous expired and valid session files are never read, Runtime token refresh cannot affect the probe, private package endpoints are never called, incomplete package pagination cannot be presented as exact Credits, concurrent probes coalesce at the Tutti boundary, and credentials or raw errors are never projected. Also cover generic renderer labels and exact amount validation for future Providers with a complete contract. Run the Extension's Linux and Windows companion checks plus Tutti's daemon, Desktop, typecheck, i18n, and changed-aware push-ready gates.
  • References: CodeBuddy Agent Extension account_usage.go agentTargetAccountUsageProbe.ts

Kimi Code account panel says the Agent provides no quota limits

  • Symptom: Kimi Code /status opens the native account panel, but its limits row says the current Agent does not provide quota limits even though the selected account uses Coding Plan.
  • Quick checks: Confirm the provider is acp:kimi-code, the active model in $KIMI_CODE_HOME/config.toml (or ~/.kimi-code/config.toml) resolves to managed:kimi-code, and the matching OAuth credential exists under the Kimi-owned credentials/ directory. Search Desktop logs for agent.usage_probe.result; logs contain only strategy/error metadata and must never contain credentials or raw responses.
  • Root cause: Kimi Code 0.28 exposes session/context usage through ACP, but it does not expose Coding Plan account windows as structured ACP data. The native panel therefore had no account-level source and treated the extension provider as unsupported.
  • Fix: Kimi's Extension declares a separately published Provider-owned account usage companion. It resolves the selected model's provider and Kimi credentials, while Tutti executes the generic accountUsage capability and consumes only Provider-neutral billing/quota fields. API-key providers return api with no quota rows. The managed provider requests Coding Plan /usages and emits normalized quota percentages/reset times. Kimi owns OAuth refresh, so the companion does not treat point-in-time expires_at or an authorization rejection as Agent login authority. It reloads the credential once after 401/403 and retries only when the token changed. Tokens, API keys, paths, raw responses, and Provider messages never cross the daemon API or renderer IPC.
  • Validation: Cover managed inline/nested OAuth configuration, Coding Plan summary/window mapping, API billing without an account request, a concurrent credential refresh, an unchanged unauthorized token that does not become session_expired, and the renderer's localized API billing label. Run the Desktop tests, typecheck, i18n check, build, and changed-aware push-ready gate.
  • References: Kimi Code Agent Extension account_usage.go agentTargetAccountUsageProbe.ts agentProviderUsageProbe.ts createDesktopAgentStatusSource.ts

Claude Code sessions fail with effectiveSource: "none" when CC-Switch or similar proxy tools are used

  • Symptom: Tutti desktop sessions for the claude-code provider never connect. The UI reports agent session is not connected even though the same Claude CLI works fine when run from a terminal session that loaded CC-Switch (or a similar ~/.claude/settings.json proxy).
  • Quick checks: In tuttid.log search for CLAUDE_CODE_AUTH_REFRESH_DEBUG. If credentials.effectiveSource is "none" and both keychain.found and plaintext.found are false, but ~/.claude/settings.json contains an env block with ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL, the sidecar never propagated the file's env to the Claude SDK.
  • Root cause: CC-Switch writes proxy credentials into ~/.claude/settings.json's env field. The native Claude CLI picks them up because the user shell exports them into process.env, but the Tutti claude-sdk-sidecar is launched directly without going through a shell, so those variables are missing. The sidecar previously only merged process.env with the ACP payload env and never read the file.
  • Fix: Read the Claude settings files in the sidecar and merge their env blocks into the Claude SDK query options, between process.env (lower priority) and the ACP payload env (higher priority). The merge covers ${CLAUDE_CONFIG_DIR}/settings.json (defaulting to ~/.claude) plus project-level .claude/settings.json / .claude/settings.local.json walking from the filesystem root to the session cwd, matching the native CLI's layering. See claudeSettingsEnv and ensureQuery in claude-sdk-sidecar main.ts. The agentstatus probe reads the same $CLAUDE_CONFIG_DIR-aware location (claudeSettingsDeclares in provider_custom_config.go) so the environment wizard and the runtime agree on whether credentials exist.
  • Validation: Run pnpm --filter @tutti-os/claude-sdk-sidecar test and cd services/tuttid && go test ./service/agentstatus/. Unit tests cover reading, non-string value skipping, missing file, malformed JSON, missing env field, user/project/local layering, and CLAUDE_CONFIG_DIR resolution.
  • Note: credentials.effectiveSource only tracks OAuth material (keychain or .credentials.json). For API-key/proxy users it stays "none" even after the fix; a connected session is the success signal, not that field.

Cursor free plan shows a red error on the next send after upgrade copy

  • Symptom: A Cursor free / exhausted account first returns plain assistant text Upgrade your plan to continue. Sending again shows a scary red turn-failed card (often with an “Open setup” escape hatch) instead of the same calm plan-gate copy.
  • Root cause: cursor-agent soft-surfaces the first plan gate as an assistant chunk + end_turn. Later attempts may fail session/prompt with the same fixed copy (upgrade / payment actions). Tutti previously treated that ACP call failure as a generic turn.failed / provider_error danger card, and the visible-error classifier did not recognize the Cursor phrases as a quota / plan limit.
  • Invariants: When ACP session/prompt fails with Cursor plan/payment gate copy, soft-settle the turn: emit a warning system notice with that copy and complete the turn (planLimit=true) so the composer stays usable without a danger card. Keep residual visible-error classification of those phrases in the quota_or_rate_limit bucket, and render that bucket with warning tone rather than danger. Do not route plan gates into the env-wizard “Open setup” path.
  • Validation: Run go test ./packages/agent/daemon/runtime -run 'PlanLimit|VisibleFailureCodeRecognizesCursorPlanLimit' and the AgentGUI visible-error / classifyFailedAgentMessage specs that cover Upgrade your plan to continue.
  • References: acp_plan_limit.go standard_acp_turn.go visible_error.go AgentMessageBlock.tsx

Tutti Agent retries a 402 and shows generic provider setup

  • Symptom: A request with insufficient Tutti credits displays Reconnecting... 5/5, then falls back to a generic provider error card whose action opens local setup instead of account plans.
  • Root cause: The billing boundary collapsed every commerce pre-deduct error into HTTP 402, the agent protocol treated every unexpected HTTP status as retryable, and the daemon classifier did not distinguish the resulting payment failure from a generic provider failure.
  • Invariants: Preserve machine-readable billing codes across commerce, token usage, the LLM gateway, and the agent runtime. Commerce exposes depleted credits as ResourceExhausted/CREDITS_INSUFFICIENT; token usage translates only that decision to the OpenAI-compatible 429 usage_limit_reached envelope with code insufficient_credits, which legacy Tutti Agent releases already treat as terminal. The gateway adds the Tutti plans promo header so the parsed terminal error retains actionable account context. Dependency failures remain 5xx. Classify actionable account failures before generic quota/provider buckets, and route account actions through the host link-action boundary rather than opening URLs directly from transcript UI.
  • Validation: Cover the commerce RPC error mapping, token-usage envelope, gateway promo header, daemon visible-error classification, and rendered plans-page action as separate boundary tests.

OpenCode effort changes fail with effort not found

  • Symptom: An OpenCode session starts successfully, but changing reasoning effort fails through session/set_config_option with Invalid params: effort not found. Big-Pickle is a common example.
  • Quick checks: Run opencode models <provider> --verbose and inspect the selected model's variants object. Compare those keys with the model-specific reasoning profile returned by the composer-options endpoint. Also inspect the live ACP configOptions[id="effort"]; a UI option that is absent from both sources must never be submitted.
  • Root cause: OpenCode's top-level capabilities.reasoning says the model can reason, but it does not mean the model exposes selectable reasoning variants. Models use different variant sets, and some models return an empty variants object. A provider-wide static low / medium / high / xhigh list therefore creates controls that the current model cannot honor.
  • Fix: Parse opencode models --verbose, preserve an explicitly empty variants profile, clear remembered effort values that are unsupported by the selected model, and refresh composer options after model changes. Before sending a live effort update, require the current ACP descriptor to advertise the exact value.
  • Validation: Cover a model with empty variants, a model with ordered low / medium / high / max variants, remembered-setting sanitization, and runtime rejection before any ACP call for an unadvertised value.

Codex or Tutti Agent model/capability catalog times out on cold start

  • Symptom: The provider status is ready, but the composer model or capability catalog temporarily falls back, reports model/list timed out, or records an app-server capability discovery timeout after the daemon has been idle.
  • Quick checks: Search the daemon log for agent.model_catalog.fetch_settled and inspect provider, durationMs, stage, and error. A provider-process timeout near 8 seconds points to the old cold-start boundary. Also inspect the provider stderr for codex_models_manager refresh failures and distinguish those from Tutti's own context deadline exceeded.
  • Root cause: Codex and Tutti Agent both launch a Codex app-server. On Windows the .cmd shim, managed Node startup, and the provider's own model metadata refresh can make the first model/list or capability request slower than the previous 8-second process bound, even though a later request succeeds and status detection remains ready. codex_runtime_selection_stale and a provider-owned child-process refresh timeout are separate runtime-selection failures; increasing Tutti's request bound does not mask them.
  • Fix: Keep the app-server process/request bound at 30 seconds for model and capability catalogs, and give Codex/Tutti Agent's outer model-catalog fetch a 35-second bound. Keep these values provider-specific; do not widen Claude, Cursor, generic ACP, or interactive session timeouts without corresponding boundary evidence.
  • Validation: Confirm a cold catalog fetch either returns a non-empty model/capability list or reports the provider-owned error after the bounded window. Verify subsequent persistent/cache-backed requests succeed, and that status detection and interactive session timeout values remain unchanged. Run cd services/tuttid && go test ./service/agent and pnpm check:changed.
  • References: codex_model_catalog.go codex_capability_catalog.go model_catalog.go

Windows Agent process remains after ACP startup timeout

  • Symptom: A Codex, Cursor, Kimi, Claude SDK, or other local process Agent times out, and a later retry reports cleanup pending, process did not exit after kill, or an apparently missing provider. The same provider may work again after a daemon restart.
  • Quick checks: Search the daemon log for agent_session.live_resource_cleanup.failed, agent_session.live_release.failed, and agent_session.process_start.group_attach_failed. On Windows, inspect the provider's .cmd/.bat wrapper and its Node or Python descendants rather than checking only the wrapper PID.
  • Root cause: A Windows command shim can exit while a descendant keeps the ACP stdout or stderr pipe open. A later taskkill by the exited root PID cannot reliably reach that descendant, so the transport close remains pending and the adapter retains ownership to prevent an unsafe replacement.
  • Fix: Local process launches attach their process tree to a Windows Job Object with KILL_ON_JOB_CLOSE. Graceful taskkill remains the first close attempt; the owned Job Object is the fallback when the shim PID has already exited. Hosts that reject nested Job Object assignment retain the existing taskkill fallback and emit agent_session.process_start.group_attach_failed.
  • Validation: Reproduce a .cmd launch whose child outlives the shim, close the transport, and verify that the child is gone or signaled as exited. Repeat with startup timeout, cancellation, replacement, daemon shutdown, and direct .exe providers. No live_resource_cleanup.failed should remain after successful cleanup.
  • References: process_transport.go process_command_windows.go

OpenCode model picker has fewer models than the terminal

  • Symptom: opencode models --verbose lists more models in a local terminal than the OpenCode model picker in Agent GUI. Custom provider ids or recently published model variants are commonly absent. A related presentation symptom shows a provider-qualified recent item such as newapi/deepseek-v4-pro, while the searchable catalog shows only the ambiguous model name DeepSeek V4 Pro.
  • Quick checks: Run opencode models --verbose from the same workspace cwd passed to the composer. Count exact provider/model lines and compare them with the composer-options model config. Confirm daemon logs do not contain composer model catalog lookup failed. A models.dev cache hit only affects image-input metadata and cannot remove a model option.
  • Root cause: OpenCode resolves project configuration relative to the command cwd. The daemon previously ran model discovery in its own inherited cwd and stored the resulting provider-wide list for six hours. A smaller result from the wrong project context therefore remained visible even after the terminal catalog changed. Separately, verbose catalog normalization previously used only the model metadata name as the display label even though the exact launch identity remained the provider-qualified provider/model id.
  • Fix: Pass the composer workspace cwd through the daemon model-catalog request and set it as the opencode models --verbose process directory. Cache a successful catalog for five minutes and a failed fetch for 30 seconds, with the cwd included in the in-memory key; never reuse one workspace's project catalog in another workspace or persist these cwd-scoped entries. Keep one request-scoped catalog projection so a composer-options request starts the CLI only once. Preserve the auth/config invalidation event so an already-open composer refreshes when global OpenCode credentials or config files change. Append a non-built-in provider id to verbose model labels while preserving the exact provider-qualified id as the selection value. Keep the built-in opencode provider suffix hidden so ordinary catalog entries stay concise, and avoid renderer-side provider branches.
  • Validation: Cover cwd propagation, repeated same-cwd cached OpenCode lookups, separate fetches for different cwds, failed-fetch throttling, all provider/model prefixes from verbose output, one catalog lookup per composer-options request, duplicate model names under different provider ids, and unchanged cache policies for Codex and Tutti Agent. Run cd services/tuttid && go test ./service/agent and pnpm check:changed.
  • References: opencode_model_catalog.go model_catalog.go composer_options.go

Agent slash palette only shows Browser

  • Symptom: Typing / in a Claude Code, Codex, or OpenCode composer shows only the Browser capability. Provider commands such as compact, status, goal, review, or plan are missing.
  • Quick checks: Call the provider composer-options endpoint and inspect slashCommandPolicy. If Codex or Claude returns a policy but the UI still shows only Browser, trace the new-session creation guard and the target-scoped composer-options cache. If one provider returns no policy, inspect its provider registry descriptor.
  • Root cause: Composer-options loading can be intentionally skipped while a new session is being created. A mount-time creation ref that never follows current engine state leaves loading permanently disabled after creation settles. Browser still appears because it is independently projected from session capabilities. A provider descriptor missing its slash policy produces the same symptom for that provider even when loading succeeds.
  • Fix: Keep the creation guard synchronized with current engine state and reload composer options on the creating-to-settled transition. Keep fallback commands and local effects in the provider registry descriptor; do not add provider-name branches in Agent GUI.
  • Validation: Cover creation settling followed by a composer-options request, provider descriptor policy projection, and slash palette composition alongside the Browser capability. Run Agent GUI, provider registry, and agent service tests.
  • References: agent-activity-packages.md useAgentGUIComposerOptionsSync.ts opencode.go

Local capability slash command reaches the provider as unknown

  • Symptom: A local capability command appears in the Agent slash palette and updates its composer setting, but submitting text such as /computer click Confirm produces a provider response such as Unknown command: /computer.
  • Quick checks: Confirm the resolved slash catalog contains a capability entry rather than a provider command. Then trace both palette selection and form submission; the latter must resolve a local capability submit effect before the generic provider slash-command path.
  • Root cause: The palette selection path enabled the capability and filled the canonical token, but the form submission path had no matching local interceptor. The raw slash invocation therefore crossed the runtime boundary and was parsed as a provider-native command.
  • Fix: Route every local capability entry through the shared capability submission parser and handoff projection. Preserve the slash invocation as displayPrompt, then dispatch one semantic submit carrying the handoff prompt plus a requiredSettingsPatch. New-session activation merges the patch into initial settings; existing-session delivery retains it in the activity queue and applies it at the host command port before sending. Do not sequence a settings mutation and a submit in a React hook. Keep provider-native command behavior descriptor authoritative; do not add provider-name branches.
  • Validation: Cover slash and alias forms, capability-disabled rejection, visible prompt normalization, handoff prompt construction, new-session setting activation, queued-prompt patch retention, and settings-before-prompt host ordering.
  • References: agent-gui-node.md agentCapabilityUseSubmit.ts agentSlashCommandProviderPolicy.ts promptQueue.reducer.ts
  • Symptom: OpenCode or another standard ACP provider completes tool calls, but Agent GUI renders generic raw-payload cards instead of terminal, edit, read, search, or todo UI. In a session without a selected project, clicking an absolute HTML or source-file path in an assistant message has no effect.
  • Quick checks: Inspect persisted tool payloads. If toolName contains a command, absolute path, or result sentence while input.kind, input.title, or rawInput identifies the actual operation, canonicalization happened too late. For file links, compare the selected project root with the durable session cwd.
  • Root cause: Standard ACP terminal updates may replace the display title with dynamic output. Persisting that title as tool identity prevents shared specialized renderers from matching the call. Older events may also retain protocol envelopes under rawInput, rawOutput, and output metadata. Separately, requiring a selected project root discards valid link actions for no-project sessions even though their cwd is authoritative.
  • Fix: Canonicalize standard ACP tools before persistence, retain the started call's identity through terminal updates, and promote protocol envelopes into the shared tool payload. Keep a provider-neutral historical projection for rows already stored in the old shape. Resolve conversation files against the selected project root or, when absent, the session cwd.
  • Validation: Cover dynamic ACP start/terminal titles, historical payload projection, specialized renderer data, direct link resolution, and a transcript click from a no-project session. Run the Agent GUI tests and daemon runtime tests.
  • References: agent-gui-node.md acp_tool_normalizer.go workspaceLinkActions.ts

Enabled Agent Extension is missing from AgentGUI

  • Symptom: The extension feature gate is enabled and its release is reachable, but no extension target appears. The daemon log may only mention a missing local active.json fallback.
  • Quick checks: Confirm the daemon process inherited the feature-gate environment variable, inspect <state>/agent/extensions/<agentKey>, and query agent_targets for extension:<agentKey>. Verify the public ZIP's signature, digest, size, entry modes, and package structure using the same daemon installation path. A version containing +local. is a development snapshot; it is intentionally ineligible once the matching package-directory override is removed.
  • Root cause: A failed remote reconciliation can be obscured when the subsequent offline fallback error replaces the original error. ZIP directory entries commonly use mode 0755; treating their search bits as executable file content rejects an otherwise valid data-only package before it can be registered. Runtime discovery can fail similarly when the daemon's strict JSON decoder does not model a signed profile field such as the standard probe declaration. A previously active local snapshot must also not silently become the offline fallback for a source that is now configured as signed remote.
  • Fix: Preserve both the remote reconciliation error and the offline fallback error. Reject symlinks for every entry, accept safe directory entries before checking executable bits, and reject executable bits only on non-directory files. Keep the daemon discovery DTO aligned with the release profile contract, including optional probe metadata, even while a later migration phase owns executing the ACP readiness probe. Treat local and remote installations as different source modes: removing the local override removes a stale local Target and requires a compatible signed remote installation, while a verified remote installation remains eligible for normal offline fallback.
  • Validation: Cover a release ZIP with explicit 0755 directory entries and non-executable data files, retain a separate executable-file rejection test, and confirm a failed remote request remains visible when no offline installation exists. Then install the published artifact in an isolated state directory and verify both active.json and extension:<agentKey>.
  • References: manager.go manager_test.go

Extension composer controls stay on Loading and environment setup says unsupported

  • Symptom: An extension Target is ready and its home composer is visible, but model or permission controls never leave Loading. Opening Environment Check says the agent has no managed environment setup.
  • Quick checks: Call the target-scoped composer-options endpoint with both the extension provider and agentTargetId. A 400 malformed_request while /v1/agent-targets reports the Target as ready points to provider identity normalization, not runtime discovery. Also confirm the config menu is not offering the desktop-managed environment wizard for an extension Target. If the endpoint returns 200 but has no models, inspect whether the ACP agent reports standard models state rather than legacy configOptions, and confirm its hidden no-project session has a daemon-managed discovery CWD.
  • Root cause: After the Agent Target had authoritatively resolved an open provider identity such as acp:gemini, composer-options normalized it again through the closed built-in provider catalog. The identity became empty and the request failed, while the renderer kept its loading projection. Separately, the config menu exposed the built-in managed-environment action for every provider even though extension readiness belongs to the Agent Target lifecycle. A second failure path used an empty CWD for no-project discovery and only understood configOptions, while Gemini reports its catalog through ACP models.
  • Fix: Preserve open provider identities only after successful Agent Target launch resolution. Keep direct provider-only requests on the closed built-in path. Show the desktop environment wizard only for providers owned by the built-in provider catalog; extension installation and readiness remain Target-owned. Give hidden extension probes a daemon-owned CWD and normalize standard ACP models into the same shared composer model descriptor.
  • Validation: Cover target-scoped composer options for an extension provider, verify the real endpoint returns 200, and confirm the extension config menu omits the desktop environment action while retaining general Agent settings. Verify the response contains the runtime-advertised model IDs without a provider-specific catalog in Tutti.
  • References: composer_options.go AgentGUINodeView.tsx

Extension login returns to the login button without an error

  • Symptom: Target setup opens the browser and waits for authentication, then silently returns to auth_required. The runtime detection row may appear to restart during each progress poll, and a pending Ready row can look like an incorrect success state while login is still running.
  • Quick checks: Inspect the setup snapshot's durable action.status, errorCode, and errorMessage, then correlate its timestamp with the ACP authenticate request in the daemon log. A successful browser callback does not prove that the runtime accepted the account; the ACP response remains authoritative.
  • Root cause: Setup polling reused the foreground refresh state, so every background request set the whole panel to loading. Explicit ACP authentication failures were also normalized into a successful auth_required probe result, losing the provider error before the durable action was written. AgentGUI then rendered errors only for top-level setup failure, not a failed authenticate action. The terminal Ready row could never become successful because the setup dialog closes as soon as the authoritative snapshot becomes ready.
  • Fix: Keep background polling non-disruptive, preserve errors from explicit ACP authenticate calls, and show failed/interrupted action details in both the existing host toast and the setup dialog while keeping retry available. Fire the toast only when the current action moves from running to failed so polling cannot repeat it and restoring an old failure cannot replay it. Do not render a terminal readiness row that only exists while setup is non-ready.
  • Validation: Cover background polling without detection loading, an ACP authenticate rejection retaining provider text through the durable setup action, one toast per current-action failure, the persistent GUI failure presentation, and absence of the misleading pending readiness row. Run Agent daemon, setup service, AgentGUI, desktop watcher, i18n, typecheck, and desktop build checks.
  • References: standard_acp_setup.go setup.go AgentTargetSetupGate.tsx agentTargetSetupNotificationController.ts

Standard ACP first-time configuration is reported as runtime installation failure

  • Symptom: A managed standard ACP runtime installs and initializes successfully, but session/new reports that no model or inference provider is configured. Target setup then labels the installed runtime as failed instead of offering the runtime's advertised terminal setup action.
  • Root cause: Authentication classification covered credential and login failures but not a runtime whose first-use gate is model-provider configuration. The setup probe therefore discarded the terminal method learned from initialize.
  • Fix: Treat an explicit missing-provider response as auth_required only when the same initialize response advertised a usable terminal configuration method. Keep the provider response as a hard runtime failure when no such method exists, so unrelated launch failures are not hidden by the setup gate.
  • Validation: Emulate initialize with a terminal setup method followed by a missing-provider session/new error and assert that setup preserves the method and returns auth_required. Also cover the no-method and unrelated error boundaries.
  • References: standard_acp_setup.go standard_acp_setup_test.go

Extension uv runtime install selects an incompatible system Python

  • Symptom: Reinstalling an Agent Extension runtime fails with install command uv failed: exit status 1. Replaying the signed uv tool install command reports that the current Python version does not satisfy the package's Requires-Python constraint. A common macOS example is the system Python 3.9 being selected for a package that requires Python 3.11 or newer.
  • Quick checks: Inspect the Extension manifest's exact uv package pin and the package's Requires-Python metadata. Reproduce with the Tutti-managed uv binary and the same UV_TOOL_DIR, UV_TOOL_BIN_DIR, UV_PYTHON_INSTALL_DIR, UV_CACHE_DIR, and UV_NO_CONFIG=1 values. If adding UV_PYTHON=3.12 resolves the package and downloads managed CPython beneath the install root, the package pin and network path are not the root cause.
  • Root cause: Setting UV_PYTHON_INSTALL_DIR confines Python versions that uv downloads, but it does not request an interpreter version. Without an explicit request, uv tool install may select the daemon's system Python before it resolves the package metadata, making a valid pinned package appear unsatisfiable.
  • Fix: Tutti's uv install environment requests Python 3.12 with UV_PYTHON=3.12 and requires uv-managed interpreters with UV_MANAGED_PYTHON=1. Keep UV_PYTHON_INSTALL_DIR inside the final runtime root so the tool remains isolated and does not depend on or mutate a system Python installation.
  • Validation: Cover the two Python selection variables in the uv installer test. Re-run the real signed package installation in an isolated root, assert the executable reports the pinned package version and Python 3.12, then run the changed-aware repository validation.
  • References: installer_uv.go installer_uv_test.go Agent Extensions

Extension runtime installation stays failed after restart

  • Symptom: An Agent Extension runtime install fails once. Reopening Tutti restores the same failure, and checking again never offers or starts another installation.
  • Quick checks: Inspect the setup snapshot. If it contains a failed or interrupted install action plus a non-null install plan, the daemon has enough information to retry. Confirm the visible action starts setup/install with a new clientActionId; a setup GET only refreshes the persisted failure.
  • Root cause: Failed setup actions are durable by design. The setup panel rendered its install button only for not_installed, while mapping failed/interrupted install actions to failed. Its generic check-again action only fetched the same durable snapshot, so restart could not change the state.
  • Fix: Keep the failed action and provider error visible. When a failed or interrupted install snapshot retains a plan, offer an explicit reinstall action that submits the plan digest with a fresh client action ID. Do not erase the previous failure or treat a GET refresh as a retry.
  • Validation: Mount the setup panel from an initial persisted failed-install snapshot, verify the error remains visible, and assert reinstall submits the retained plan with a client action ID different from the failed action.
  • References: setup.go AgentTargetSetupGate.tsx agentTargetSetupController.tsx

Vertex setup reports ready but the first prompt cannot load credentials

  • Symptom: Selecting Gemini vertex-ai completes Target setup and creates a session, but the first prompt fails with Could not load the default credentials or reports missing GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION, or an API key. The empty-home setup gate is gone, leaving no obvious way to change the login method.
  • Quick checks: Correlate the durable authenticate action with ACP traffic. If authenticate returns an empty result and session/new succeeds, but the later formal session/prompt returns the credential error, setup observed a runtime false positive rather than losing the selected method.
  • Root cause: Gemini can defer Vertex ADC and project/location validation until a real prompt. ACP exposes no credential-validation request stronger than the runtime's own authenticate plus session/new, so setup cannot prove request usability without sending user-visible work. Extension Target setup was also mounted only in empty-home state, unlike the persistent built-in provider environment entry.
  • Fix: Classify the formal prompt's credential error as an authentication failure and feed it into Target setup detection. Override a later otherwise-ready ACP probe to auth_required. Expose the same Target setup dialog from the selected provider's config menu in both ready and non-ready states, permit explicit re-authentication from ready, and reuse one Target watch across the two UI hosts.
  • Validation: Cover the real Vertex ADC error text, auth invalidation overriding a ready probe, re-authentication clearing invalidation, ready-state auth method selection, and one cached desktop watch per Target. Do not add a hidden synthetic prompt to setup.
  • References: agent_run_outcome_reporter.go setup.go AgentGUINodeView.tsx AgentTargetSetupGate.tsx

Extension messages appear sent but show no running or failure state

  • Symptom: A new extension conversation displays the user message, but the composer immediately looks idle and no provider response or error card appears.
  • Quick checks: Trace one Agent Session from runtime.submitted through the standard ACP session/prompt call. If the adapter logs a provider error and runtime.events_emitted reports empty event types, inspect provider identity normalization before debugging renderer polling or streaming.
  • Root cause: The Agent Target and runtime accepted the extension-owned provider ID, but the shared activity event context still resolved providers through the closed built-in catalog. Turn-started, user-message, and turn-failed events for identities such as acp:gemini became empty events, so neither running state nor the real provider error reached durable conversation state.
  • Fix: Centralize the canonical open-provider format in the provider registry and reuse it for both authorized service requests and activity event identities. Keep launch authorization separate: accepting an identity as event metadata does not authorize a runtime without a fixed Agent Target reference.
  • Validation: Cover open extension identities in provider-registry and activity-event tests, then project turn.started and turn.failed for an extension session and assert both retain the extension provider ID.
  • References: registry.go activity_types.go activity_projection.go

Extension sessions show an open provider ID or disappear from mentions

  • Symptom: An extension works in AgentGUI, but message-center cards or @session rows show a raw identity such as acp:gemini with the generic multi-Agent icon. The same extension may be absent from the @agent Agents tab.
  • Quick checks: Read the extension Agent Target from /v1/agent-targets or the local target store and confirm it has the expected name and signed icon URL. Then compare the affected session's agentTargetId. If both are correct, inspect whether the renderer projection still calls the built-in provider catalog or provider icon resolver instead of the Agent Directory.
  • Root cause: Runtime provider and product agentTargetId are different identities. Built-in providers happened to render correctly when older consumers used provider for both, but an open extension provider has no built-in catalog entry and therefore degrades to raw text/generic artwork or is filtered out.
  • Fix: Resolve session and message-center presentation by exact agentTargetId against the shared Agent Directory. Build @agent candidates directly from ready, enabled Agent Targets; use the built-in provider catalog only for optional built-in visibility gates, never as extension authorization or display metadata.
  • Validation: Cover an enabled extension:* Target with an acp:* provider and assert the Agents tab, Agent Session rows, and message-center cards all use the Target name and icon. Also retain coverage for historical provider-only sessions.
  • References: desktopRichTextAtAgentContributors.ts workspaceAgentMessageCenterModel.ts agent-gui-node.md

Extension failure card appears while processing never stops

  • Symptom: A standard ACP extension displays the provider error card, but the transcript still shows the processing indicator and the conversation remains busy.
  • Quick checks: Compare the terminal turn.failed runtime log with the streamed session projection. If the runtime reports failed / settled while the renderer still has the same activeTurnId in running, inspect the lifecycle data on the terminal activity event rather than changing the processing-row UI.
  • Root cause: The standard ACP adapter emitted explicit turn events without authoritative lifecycle snapshots. Built-in providers could still settle through their registered event projection policy, but an extension provider was not in that closed catalog. Its error message persisted while the prior running turn reference remained active.
  • Fix: Stamp every standard ACP turn transition with a sequenced adapter-origin lifecycle snapshot. The reporter then copies the provider-independent snapshot, so a terminal failure atomically records the error outcome, marks the turn settled, clears activeTurnId, and re-enables submission.
  • Validation: Cover a standard ACP start/failure pair and assert adapter-origin snapshots progress from running with an active turn ID to settled / failed with no active turn ID. Run the runtime and service regression suites.
  • References: standard_acp_turn.go turn_lifecycle_stamp.go reporter_state.go

Extension slash palette is empty or ignores its command filter

  • Symptom: Typing / in an extension conversation opens no command or Skill list, while the ACP process otherwise starts successfully. A related failure shows every provider-advertised command instead of the signed profile's smaller catalog.
  • Quick checks: Inspect the persisted session internal_runtime_context_json. If commands contains provider command names, the ACP command update was received and the remaining fault is command hydration or filtering. Confirm the composer request uses the active Session's exact agentTargetId, then compare the response commands and slashCommandPolicy with the installed profiles/composer.json. Skills remain empty unless the profile declares validated roots and the matching capabilities profile advertises Skill support.
  • Root cause: Runtime command updates were available only through a transient renderer event. A renderer that subscribed after the startup update, or reloaded an existing session, had no command catalog even though the daemon retained it. The slash palette also discarded every provider command when no built-in slash-command policy existed. That condition is normal for an open extension provider, so a valid ACP command catalog could still render as empty after hydration succeeded. Open extension providers also have no built-in composer profile, so the built-in provider Skill discovery table correctly returned no roots. Active-session composer reads could also fall back to node-level provider metadata and miss the extension Target. Conversely, an authoritative signed catalog that repeated every ACP command correctly preserved every command; that was a package declaration error, not a renderer filtering failure.
  • Fix: Persist the detailed ACP command catalog in session runtime context and let composer options restore it when no live engine snapshot is present. Treat provider-advertised commands as runtime capabilities even without a built-in policy, and keep their selection provider-native. Scope active-session composer reads and cache lookup by the Session's exact agentTargetId. Declare only the intended product command subset in an authoritative signed catalog; do not add a provider-name filter in AgentGUI. Declare extension Skill roots, invocation, and trigger prefix in the signed composer profile; resolve only safe relative workspace/user paths.
  • Validation: Cover startup command projection, legacy command-name recovery, composer option parsing, active-session Target selection, authoritative command narrowing, declared extension Skill roots, and unsafe path rejection.
  • References: standard_acp_settings.go composer_commands.go profiles.go agentSlashCommandProviderPolicy.ts

Codex Model Plan turns fail or stay waiting against a Chat-only endpoint

  • Symptom: A Codex session bound to an OpenAI-protocol Model Plan fails immediately, stays working without output, or loses tool-call messages. The Plan's connection check can still pass because detection calls /v1/chat/completions directly. Another immediate-failure shape is a terminal provider error such as metadata value too long: ... (578 > 512) after a short prompt that produced no assistant content.
  • Quick checks: Inspect the session-scoped Codex config.toml. The tutti-model-plan provider must use a loopback base_url, a temporary TUTTI_MODEL_PLAN_API_KEY, and wire_api = "responses". Verify the upstream server receives /v1/chat/completions, not /v1/responses. A direct Chat-only Base URL paired with wire_api = "responses" is incomplete. For the metadata failure, inspect the exported Session's terminal Turn error and compare the reported value length with 512. Codex workspace diagnostics can grow with Git remotes and usage-attribution fields. Adjacent model-list 404s are not the terminal cause when the thread and Turn both start successfully.
  • Root cause: Current Codex emits Responses-shaped requests and requires terminal Responses SSE events. A Chat-only provider neither owns /v1/responses nor emits the response.output_item.* and response.completed state machine. Renaming Chat deltas or changing only wire_api leaves Codex waiting or discarding output. Current Codex can also advertise its built-in hosted web_search tool by default, and later versions may advertise other hosted tools that Chat Completions cannot execute. Codex also sends Responses developer messages; Chat-compatible providers that only recognize system/user/assistant/tool can reject the otherwise valid request during tokenization. The gateway also used to forward Responses metadata/client_metadata unchanged. Codex can encode its workspace diagnostics as one optional metadata value larger than the 512-byte limit enforced by common Chat-compatible endpoints, so the upstream rejects the request before model execution.
  • Fix: Keep Codex on wire_api = "responses" and route the session through tuttid's loopback Model Gateway. The gateway authenticates the temporary session token, converts supported Responses inputs to Chat Completions, forwards with the daemon-held Plan credential, and reconstructs complete Responses JSON/SSE output. The gateway filters non-translatable entries only from the per-request tool registration list and removes orphaned tool_choice/parallel_tool_calls controls. It still rejects explicit selection of a filtered tool and hosted call/output history. This avoids version-specific Codex config mutations while preserving fail-closed semantics for requested or recorded tool use. Its Codex role normalization matches cc-switch: developer becomes system, latest_reminder and unknown internal roles become user, text-only content-part arrays are newline-joined, and textual system messages are merged in order at message index zero. This preserves instruction precedence without requiring newer OpenAI-only roles or mid-conversation system roles from the upstream tokenizer. OpenCode continues to use the Plan endpoint directly. Before sending the converted Chat request, omit only metadata values larger than 512 bytes. Do not truncate them, because a truncated diagnostic JSON value is misleading and may be invalid. Keep the original Responses metadata for local response reconstruction; metadata at or below the limit and Responses-over-client key precedence remain unchanged.
  • Validation: Cover request/tool conversion, interleaved parallel tool arguments, UTF-8 and arbitrary SSE byte boundaries, large arguments, usage, upstream errors, timeout/cancel/disconnect paths, route isolation, token replacement, cleanup, immutable-revision resume, mixed supported/hosted registrations, future unknown registration types, explicit hosted tool choices/history, and internal-role normalization and system-message collapse. A real smoke test must complete two Codex turns and one tool call while the upstream records /v1/chat/completions without any upstream developer role or system message after index zero. Cover the metadata boundary explicitly: a 512-byte value is forwarded, a 513-byte value is omitted, and an omitted Responses value is not replaced by lower-priority client metadata with the same key.
  • References: model-access-plans.md gateway.go responses_request.go stream_converter.go model_endpoint.go

Enabled Agent Extensions delay every daemon startup

  • Symptom: tutti.parent_monitor.started is followed by a multi-second silent gap before tutti.managed_runtime.profile_preload_started and tutti.listen. The gap grows as more Agent Extension feature flags are enabled.
  • Quick checks: Compare the two timestamps and inspect feature_flags_json in the active desktop_preferences row. Time each enabled source's signed versions.json; the old startup path fetched the enabled indexes serially before constructing the daemon API.
  • Root cause: Agent Extension reconciliation combined two different jobs: restoring an already verified local installation and checking its remote release index. The daemon needed the first job before serving the Agent Target catalog, but synchronously waited for the second job too. Multiple CloudFront TLS and response waits therefore accumulated on every restart.
  • Fix: Restore and verify cached active installations synchronously, register their Targets, and move remote release refresh after successful daemon API construction into the background. Keep synchronous reconciliation when an enabled source has no usable local installation, and for explicit preference activation changes, so the initial or newly enabled Target does not disappear from the next catalog read. Release the reconciliation lock between background source refreshes so a preference change does not wait for the complete remote batch.
  • Validation: Cover cached restore without any network request, missing-cache fallback to synchronous reconciliation, disabled Target removal, offline fallback, and preference-driven enable/disable. On a state root with cached enabled extensions, verify tutti.agent_extension.refresh_started no longer delays tutti.listen and later reaches tutti.agent_extension.refresh_completed.
  • References: agent-extensions.md manager.go wiring_daemon_api.go

Kimi setup opens a browser before showing the platform selector

  • Symptom: Clicking the Kimi setup action immediately opens the Kimi website instead of showing the Kimi Code TUI selector for OAuth or a Platform API key.
  • Quick checks: Inspect the exact terminal launch. kimi login and starting kimi followed by /login are different interfaces: the former may start a device-code flow and open a browser, while the latter opens the interactive platform selector inside the running TUI. Confirm the installed runtime's login --help and verify the welcome marker still appears before assuming the two paths are equivalent.
  • Root cause: A terminal authentication profile projected the TUI slash command as a CLI subcommand. Provider-owned commands with the same spelling do not necessarily share behavior across those two command surfaces.
  • Fix: Declare the signed authentication method as runtime-slash-command, with one safe command name and a bounded literal ready marker. Launch the bare runtime, wait for that marker on the matching terminal session, then submit the Desktop-generated slash command through the terminal transport. The daemon and AgentGUI Host boundary must carry one typed startup action rather than independent raw input and marker fields. Do not put raw terminal input or shell source in the extension profile.
  • Validation: Cover output split across terminal events, output received before the session is armed, unrelated terminal sessions, timeout, and transport failure. In a fresh isolated Kimi home, verify setup first shows the in-TUI platform selector and opens a browser only after the user chooses OAuth.
  • References: agent-extensions.md runtime_probe.go workbenchTerminalLoginPresenter.ts

Kimi Code remains in setup or reports login after authentication

  • Symptom: Kimi Code remains blocked after terminal login, has no selectable model, or reports an authentication problem when the account actually lacks an eligible plan, has insufficient balance, or reached its billing-cycle limit.

  • Quick checks: Build the extension package first, then pass its unpacked package directory:

    cd /path/to/agent-extension-kimi-code
    pnpm package:tutti-agent
    cd /path/to/tutti
    DEV_GUI_KIMI_CODE_PACKAGE_DIR=/path/to/agent-extension-kimi-code/build/tutti-agent/package \
      make dev-gui
    

    An explicit directory must exist and contain tutti.agent.json; dev-gui.sh now rejects stale source-tree paths before Electron starts. Inspect the setup snapshot reason and the structured conversation error code, not only raw text that may mention API keys or OAuth credentials.

  • Root cause: Kimi's model endpoint may wrap membership, model-plan, balance, and quota responses in an authentication-shaped error. Classifying the credential words first sends the user back through login and hides the actionable account state. Separately, restoring a cached local extension snapshot can hide an invalid or outdated development package path.

  • Fix: Classify subscription, model access, HTTP 402 balance, and quota markers before generic authentication. Project the stable reason through setup and localized AgentGUI copy without rendering raw provider payloads. For local overrides, synchronously snapshot the configured directory on every daemon start and remove the stale Target if validation fails.

  • Validation: Cover official membership/plan/quota message shapes that also mention credentials, setup reason projection, localized account-state presentation, invalid local paths, changed local package bytes, and preservation of the Target enabled preference after a successful resnapshot.

  • References: agent-extensions.md runtime-overrides.md visible_error.go setup.go

Terminal login succeeds but the setup terminal remains open

  • Symptom: The provider's terminal login reports success and returns to its normal TUI, but AgentGUI keeps the setup terminal open and continues polling the Target as auth_required.
  • Quick checks: Confirm the Desktop terminal diagnostic reports the startup action as submitted, then inspect repeated Target setup probes. Compare the fresh ACP initialize response with session/new: some runtimes keep advertising a terminal login method even after authentication succeeds.
  • Root cause: ACP authMethods is a catalog of available authentication methods, not the current authentication state. Treating a terminal-only catalog as an immediate auth_required verdict skips session/new, so a successfully configured runtime can never become ready and the Host never closes its login-terminal handle.
  • Fix: Preserve the advertised methods for presentation, but verify readiness with the bounded setup session/new probe. Continue mapping explicit authentication, missing-model, missing-provider, and terminal-method timeout outcomes to auth_required. Do not scrape terminal output or inject an exit command to infer login completion.
  • Validation: Cover a runtime that still advertises only a terminal login method while session/new returns a usable model, plus unconfigured runtimes whose session/new returns no usable model, a missing-provider error, or a timeout.
  • References: standard_acp_setup.go desktopTerminalLoginReadinessMonitor.ts

Cursor intermittently fails to start or reports a failed file read as completed

  • Symptom: Cursor ACP sometimes reports Cursor failed to start even though initialize succeeded. Newer Cursor releases can instead reach the generic 30-second session/new deadline while attaching a session-scoped HTTP MCP server. A file-read tool can also appear completed while its result is Error: Aborted and the raw diagnostic reports a disconnected TLS socket.
  • Quick checks: Correlate the same agent_session in the daemon log. The startup signature is session/new returning JSON-RPC -32603 with Failed to initialize session services. For the read signature, inspect the ACP tool update's result and rawErrorMessages; do not rely only on isError/is_error. A timeout exactly 30 seconds after session/new with no provider response is the slower MCP-client initialization signature.
  • Root cause: Cursor can transiently fail its session services after the ACP initialize handshake. Standard ACP startup previously returned that first failure immediately. Cursor 2026.08 can also take longer than the generic ACP startup budget while it establishes the supplied HTTP MCP transport. Cursor can return a successful-looking tool envelope with the failure text in provider-specific fields, which the status and error projection did not inspect or preserve.
  • Fix: Retry only the matching Cursor session/new error once on the same initialized connection, before a provider session id or user Turn exists. Give Cursor a provider-scoped 75-second initialize/session-new window; do not increase the shared ACP timeout for every provider. Normalize known aborted/socket-disconnect markers in output/error envelopes as call.failed, and promote both the result and raw transport diagnostics into the failed payload. Do not automatically replay an arbitrary file tool operation.
  • Validation: Cover a transient Cursor session-service error followed by success, assert one process and two session/new calls, and reject retries for authentication, unrelated internal errors, and other methods. Assert the Cursor-only startup bound without changing the generic ACP bound. Cover aborted/TLS tool output, preserve its diagnostic text, and keep normal output and input-side error strings from changing the status. Run the native Windows runtime lane for process/ACP behavior.
  • References: acp_provider_cursor.go standard_acp_session.go acp_tool_error_status.go acp_tool_normalizer.go