feat(agent-env): unify provider diagnostics and harden detection#1049
feat(agent-env): unify provider diagnostics and harden detection#1049devRickyyy wants to merge 3 commits into
Conversation
Signed-off-by: rick <contactchienlin@gmail.com>
Signed-off-by: rick <contactchienlin@gmail.com>
Signed-off-by: rick <contactchienlin@gmail.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9d530f4. Configure here.
| providers: [params.provider], | ||
| includeNetwork: true | ||
| }); | ||
| } |
There was a problem hiding this comment.
Tab switch reuses dock focus
High Severity
A deep-link focus (e.g., install, auth) persists globally when the environment panel opens. Switching provider tabs re-attaches the wizard, which resets its auto-start deduplication state. This allows the global deep-link focus to incorrectly trigger an auto-start action for the newly selected provider, rather than the one originally targeted by the deep-link.
Reviewed by Cursor Bugbot for commit 9d530f4. Configure here.
| export function buildAgentEnvWizardViewModel( | ||
| input: AgentEnvWizardViewModelInput | ||
| ): AgentEnvWizardViewModel { | ||
| const { status, activeAction, provider } = input; |
There was a problem hiding this comment.
Codex unparseable shown as outdated
Medium Severity
When the daemon reports codex_version_unparseable with availability unknown and only a refresh action, the wizard still treats the reason code as a CLI version failure via reasonCodeIndicatesCliVersionUnsupported, marking the install stage as error and surfacing the outdated-CLI remediation path instead of an unknown-version / re-detect flow.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 9d530f4. Configure here.
There was a problem hiding this comment.
15 issues found across 59 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/desktop/src/renderer/src/features/workspace-agent/ui/AgentEnvPanel.tsx">
<violation number="1" location="apps/desktop/src/renderer/src/features/workspace-agent/ui/AgentEnvPanel.tsx:164">
P3: A provider tab stops showing activity during install/login because the computed `projection.pending` value is ignored and only `checking` pulses. Including `projection.pending` in the pulse condition would preserve progress feedback after users switch tabs.</violation>
<violation number="2" location="apps/desktop/src/renderer/src/features/workspace-agent/ui/AgentEnvPanel.tsx:253">
P2: Switching to Hermes/OpenClaw while an anomaly consent prompt is open leaves that prompt visible, and accepting it reports the newly selected unsupported provider rather than the provider that raised the anomaly. The report state should be cleared or scoped to its originating provider whenever tab selection changes, including unsupported tabs.</violation>
</file>
<file name="apps/desktop/src/renderer/src/features/workspace-agent/services/internal/agentEnvWizardController.ts">
<violation number="1" location="apps/desktop/src/renderer/src/features/workspace-agent/services/internal/agentEnvWizardController.ts:109">
P2: Rapid tab switching during the panel's initial status load can leave the newly active provider without its network diagnostic. Concurrent `refresh()` continuations can coalesce B's request onto A's provider-scoped request, so this path should queue/preserve each provider/options request or retry only the still-attached tab.</violation>
</file>
<file name="services/tuttid/service/agentstatus/service.go">
<violation number="1" location="services/tuttid/service/agentstatus/service.go:626">
P2: Malformed or future Codex versions can still be reported as supported because `parseCLIVersion` truncates an invalid token to a valid semver prefix before this check. Preserve and validate the complete Codex version token, or add token boundaries so `codex_version_unparseable` is emitted for these formats.</violation>
<violation number="2" location="services/tuttid/service/agentstatus/service.go:628">
P2: The new `codex_version_unparseable` reason code sets `availability: unknown` with only a refresh action, but the wizard view model's `reasonCodeIndicatesCliVersionUnsupported` check likely matches this code and treats it as an install-stage error — surfacing the outdated-CLI remediation path (manual install command) instead of a distinct unknown-version / re-detect flow. Consider either excluding `codex_version_unparseable` from the CLI-unsupported predicate in the view model, or adding an explicit branch that maps this reason to an appropriate "version could not be determined" UX.</violation>
<violation number="3" location="services/tuttid/service/agentstatus/service.go:724">
P2: Explicit Cursor probes now launch the adapter twice, adding roughly two observation windows and duplicating process side effects. `Service.Probe` should reuse the probe performed by `statusForSpec`, or status calculation should support skipping its internal launch probe for this path.</violation>
</file>
<file name="services/tuttid/service/agentstatus/service_helpers.go">
<violation number="1" location="services/tuttid/service/agentstatus/service_helpers.go:219">
P2: A concurrent runtime 401 can be erased after the auth command has already returned, causing diagnostics to report authenticated despite the newer failure. Clear conditionally only when the stored invalidation still equals `failedAt` (for example, via an atomic compare-and-clear store method).</violation>
<violation number="2" location="services/tuttid/service/agentstatus/service_helpers.go:476">
P2: A Codex CLI with unparseable version is exposed by `ProbeProvider` as `codex_runtime_error`, losing the new diagnostic reason. Add `CodexErrVersionUnknown -> codex_version_unparseable` to `codexReasonCodeFromErrorCode`.</violation>
</file>
<file name="apps/desktop/src/renderer/src/features/workspace-agent/ui/agentEnvPanelSelection.ts">
<violation number="1" location="apps/desktop/src/renderer/src/features/workspace-agent/ui/agentEnvPanelSelection.ts:29">
P2: An explicit open for an unmanaged/future provider silently selects the last/default visible provider, so the panel can display and run remediation for the wrong agent instead of showing the requested provider as unsupported. The resolver should distinguish preference-hidden managed providers from unknown providers and preserve the latter for the unsupported view.</violation>
</file>
<file name="services/tuttid/service/agentstatus/codex_auth.go">
<violation number="1" location="services/tuttid/service/agentstatus/codex_auth.go:28">
P2: Diagnostics can report Codex ready when `auth_mode` selects a different credential type than the populated field, because both credential branches ignore the discriminator. Validate the declared mode against the matching API-key/token shape, while preserving explicitly supported legacy mode-less files.</violation>
<violation number="2" location="services/tuttid/service/agentstatus/codex_auth.go:32">
P2: A token marker missing `tokens.id_token` is reported authenticated even though Codex cannot deserialize its required `TokenData`. Include and require the nonblank `id_token` when recognizing the current ChatGPT token shape.</violation>
</file>
<file name="apps/desktop/src/renderer/src/features/workspace-agent/ui/useAgentEnvWizard.ts">
<violation number="1" location="apps/desktop/src/renderer/src/features/workspace-agent/ui/useAgentEnvWizard.ts:108">
P1: Switching tabs after a focused deep-link can auto-install or start login for the newly selected provider because the original `request.focus` is reused with `activeProvider`. Clear or scope the focus once the selected tab differs from the provider targeted by the request.</violation>
</file>
<file name="services/tuttid/service/agentstatus/opencode_auth.go">
<violation number="1" location="services/tuttid/service/agentstatus/opencode_auth.go:29">
P3: Diagnostics can alternate the displayed account/provider metadata when `auth.json` contains multiple usable credentials because this selects the first entry from an unordered Go map. Sorting provider IDs before selection, or omitting single-account metadata for a multi-provider store, would make status stable.</violation>
<violation number="2" location="services/tuttid/service/agentstatus/opencode_auth.go:51">
P2: Malformed OAuth records with only one token or no `expires` are reported as authenticated. OpenCode requires `access`, `refresh`, and nonnegative `expires`; decoding `expires` and validating the complete record would keep the daemon verdict conservative.</violation>
</file>
<file name="services/tuttid/wiring.go">
<violation number="1" location="services/tuttid/wiring.go:316">
P3: In the current wiring, both `ProviderStatusLister` and `AvailabilityChecker` are assigned the same `agentStatusService`. Because `AvailabilityChecker` is non-nil, `ListProviderAvailability` always goes through it and never exercises the `ProviderStatusLister` fallback added in `provider_availability.go`. The `ProviderStatusLister` path is dormant in production. Consider whether both are needed, or if one of the two wiring lines is sufficient for the current setup.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| [request.provider, snapshot.defaultProvider] | ||
| () => | ||
| resolveActiveProvider( | ||
| activeProvider ?? request.provider, |
There was a problem hiding this comment.
P1: Switching tabs after a focused deep-link can auto-install or start login for the newly selected provider because the original request.focus is reused with activeProvider. Clear or scope the focus once the selected tab differs from the provider targeted by the request.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/src/features/workspace-agent/ui/useAgentEnvWizard.ts, line 108:
<comment>Switching tabs after a focused deep-link can auto-install or start login for the newly selected provider because the original `request.focus` is reused with `activeProvider`. Clear or scope the focus once the selected tab differs from the provider targeted by the request.</comment>
<file context>
@@ -91,16 +95,20 @@ export function useAgentEnvWizard(input: {
- [request.provider, snapshot.defaultProvider]
+ () =>
+ resolveActiveProvider(
+ activeProvider ?? request.provider,
+ snapshot.defaultProvider
+ ),
</file context>
| value={provider} | ||
| onValueChange={(nextProvider) => { | ||
| lastSelectedProviderRef.current = nextProvider; | ||
| setSelection({ |
There was a problem hiding this comment.
P2: Switching to Hermes/OpenClaw while an anomaly consent prompt is open leaves that prompt visible, and accepting it reports the newly selected unsupported provider rather than the provider that raised the anomaly. The report state should be cleared or scoped to its originating provider whenever tab selection changes, including unsupported tabs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/src/features/workspace-agent/ui/AgentEnvPanel.tsx, line 253:
<comment>Switching to Hermes/OpenClaw while an anomaly consent prompt is open leaves that prompt visible, and accepting it reports the newly selected unsupported provider rather than the provider that raised the anomaly. The report state should be cleared or scoped to its originating provider whenever tab selection changes, including unsupported tabs.</comment>
<file context>
@@ -111,6 +241,22 @@ export function AgentEnvPanel({
+ value={provider}
+ onValueChange={(nextProvider) => {
+ lastSelectedProviderRef.current = nextProvider;
+ setSelection({
+ provider: nextProvider,
+ requestSequence: request.requestSequence
</file context>
| // Opening or switching to a tab is the point where its deep diagnostic is | ||
| // allowed to run. Always refresh here so an earlier local-only badge load | ||
| // cannot turn the network-enabled wizard request into a cache hit. | ||
| void params.service.refresh([params.provider], { includeNetwork: true }); |
There was a problem hiding this comment.
P2: Rapid tab switching during the panel's initial status load can leave the newly active provider without its network diagnostic. Concurrent refresh() continuations can coalesce B's request onto A's provider-scoped request, so this path should queue/preserve each provider/options request or retry only the still-attached tab.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/src/features/workspace-agent/services/internal/agentEnvWizardController.ts, line 109:
<comment>Rapid tab switching during the panel's initial status load can leave the newly active provider without its network diagnostic. Concurrent `refresh()` continuations can coalesce B's request onto A's provider-scoped request, so this path should queue/preserve each provider/options request or retry only the still-attached tab.</comment>
<file context>
@@ -103,14 +103,10 @@ export function attachAgentEnvWizard(
+ // Opening or switching to a tab is the point where its deep diagnostic is
+ // allowed to run. Always refresh here so an earlier local-only badge load
+ // cannot turn the network-enabled wizard request into a cache hit.
+ void params.service.refresh([params.provider], { includeNetwork: true });
const clearRevealTimer = (): void => {
</file context>
| return true | ||
| } | ||
| return spec.Provider == agentprovider.Codex && s.executableFile(runtimeResolution.AdapterPath) | ||
| return (spec.Provider == agentprovider.Codex || spec.Provider == agentprovider.Cursor) && |
There was a problem hiding this comment.
P2: Explicit Cursor probes now launch the adapter twice, adding roughly two observation windows and duplicating process side effects. Service.Probe should reuse the probe performed by statusForSpec, or status calculation should support skipping its internal launch probe for this path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At services/tuttid/service/agentstatus/service.go, line 724:
<comment>Explicit Cursor probes now launch the adapter twice, adding roughly two observation windows and duplicating process side effects. `Service.Probe` should reuse the probe performed by `statusForSpec`, or status calculation should support skipping its internal launch probe for this path.</comment>
<file context>
@@ -704,10 +715,14 @@ func (s Service) statusForSpec(ctx context.Context, spec ProviderSpec, now time.
return true
}
- return spec.Provider == agentprovider.Codex && s.executableFile(runtimeResolution.AdapterPath)
+ return (spec.Provider == agentprovider.Codex || spec.Provider == agentprovider.Cursor) &&
+ s.executableFile(runtimeResolution.AdapterPath)
}
</file context>
| availability.Status = AvailabilityNotInstalled | ||
| availability.ReasonCode = codexReasonCodeFromErrorCode(string(CodexErrPlatformPkgIncomplete)) | ||
| actions = append(actions, daemonAction(ActionInstall)) | ||
| } else if spec.Provider == agentprovider.Codex && !codexVersionParseable(cliVersion) { |
There was a problem hiding this comment.
P2: Malformed or future Codex versions can still be reported as supported because parseCLIVersion truncates an invalid token to a valid semver prefix before this check. Preserve and validate the complete Codex version token, or add token boundaries so codex_version_unparseable is emitted for these formats.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At services/tuttid/service/agentstatus/service.go, line 626:
<comment>Malformed or future Codex versions can still be reported as supported because `parseCLIVersion` truncates an invalid token to a valid semver prefix before this check. Preserve and validate the complete Codex version token, or add token boundaries so `codex_version_unparseable` is emitted for these formats.</comment>
<file context>
@@ -617,6 +623,10 @@ func (s Service) statusForSpec(ctx context.Context, spec ProviderSpec, now time.
availability.Status = AvailabilityNotInstalled
availability.ReasonCode = codexReasonCodeFromErrorCode(string(CodexErrPlatformPkgIncomplete))
actions = append(actions, daemonAction(ActionInstall))
+ } else if spec.Provider == agentprovider.Codex && !codexVersionParseable(cliVersion) {
+ availability.Status = AvailabilityUnknown
+ availability.ReasonCode = "codex_version_unparseable"
</file context>
| case "api": | ||
| return strings.TrimSpace(record.Key) != "" | ||
| case "oauth": | ||
| return strings.TrimSpace(record.Access) != "" || strings.TrimSpace(record.Refresh) != "" |
There was a problem hiding this comment.
P2: Malformed OAuth records with only one token or no expires are reported as authenticated. OpenCode requires access, refresh, and nonnegative expires; decoding expires and validating the complete record would keep the daemon verdict conservative.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At services/tuttid/service/agentstatus/opencode_auth.go, line 51:
<comment>Malformed OAuth records with only one token or no `expires` are reported as authenticated. OpenCode requires `access`, `refresh`, and nonnegative `expires`; decoding `expires` and validating the complete record would keep the daemon verdict conservative.</comment>
<file context>
@@ -0,0 +1,57 @@
+ case "api":
+ return strings.TrimSpace(record.Key) != ""
+ case "oauth":
+ return strings.TrimSpace(record.Access) != "" || strings.TrimSpace(record.Refresh) != ""
+ case "wellknown":
+ return strings.TrimSpace(record.Key) != "" && strings.TrimSpace(record.Token) != ""
</file context>
| actions = append(actions, daemonAction(ActionInstall)) | ||
| } else if spec.Provider == agentprovider.Codex && !codexVersionParseable(cliVersion) { | ||
| availability.Status = AvailabilityUnknown | ||
| availability.ReasonCode = "codex_version_unparseable" |
There was a problem hiding this comment.
P2: The new codex_version_unparseable reason code sets availability: unknown with only a refresh action, but the wizard view model's reasonCodeIndicatesCliVersionUnsupported check likely matches this code and treats it as an install-stage error — surfacing the outdated-CLI remediation path (manual install command) instead of a distinct unknown-version / re-detect flow. Consider either excluding codex_version_unparseable from the CLI-unsupported predicate in the view model, or adding an explicit branch that maps this reason to an appropriate "version could not be determined" UX.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At services/tuttid/service/agentstatus/service.go, line 628:
<comment>The new `codex_version_unparseable` reason code sets `availability: unknown` with only a refresh action, but the wizard view model's `reasonCodeIndicatesCliVersionUnsupported` check likely matches this code and treats it as an install-stage error — surfacing the outdated-CLI remediation path (manual install command) instead of a distinct unknown-version / re-detect flow. Consider either excluding `codex_version_unparseable` from the CLI-unsupported predicate in the view model, or adding an explicit branch that maps this reason to an appropriate "version could not be determined" UX.</comment>
<file context>
@@ -617,6 +623,10 @@ func (s Service) statusForSpec(ctx context.Context, spec ProviderSpec, now time.
actions = append(actions, daemonAction(ActionInstall))
+ } else if spec.Provider == agentprovider.Codex && !codexVersionParseable(cliVersion) {
+ availability.Status = AvailabilityUnknown
+ availability.ReasonCode = "codex_version_unparseable"
+ actions = append(actions, Action{ID: ActionRefresh, Kind: ActionKindRefresh})
} else if spec.Provider == agentprovider.Codex && !codexVersionMeetsMinimum(cliVersion) {
</file context>
| <span>{resolveProviderLabel(candidate)}</span> | ||
| <StatusDot | ||
| ariaLabel={statusLabel} | ||
| pulse={projection.status === "checking"} |
There was a problem hiding this comment.
P3: A provider tab stops showing activity during install/login because the computed projection.pending value is ignored and only checking pulses. Including projection.pending in the pulse condition would preserve progress feedback after users switch tabs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/src/features/workspace-agent/ui/AgentEnvPanel.tsx, line 164:
<comment>A provider tab stops showing activity during install/login because the computed `projection.pending` value is ignored and only `checking` pulses. Including `projection.pending` in the pulse condition would preserve progress feedback after users switch tabs.</comment>
<file context>
@@ -42,11 +129,54 @@ export function AgentEnvPanel({
+ <span>{resolveProviderLabel(candidate)}</span>
+ <StatusDot
+ ariaLabel={statusLabel}
+ pulse={projection.status === "checking"}
+ title={statusLabel}
+ tone={resolveStatusDotTone(projection.status)}
</file context>
| pulse={projection.status === "checking"} | |
| pulse={ | |
| projection.pending || projection.status === "checking" | |
| } |
| if err := json.Unmarshal(raw, &payload); err != nil || payload == nil { | ||
| return AuthInfo{}, false | ||
| } | ||
| for providerID, rawRecord := range payload { |
There was a problem hiding this comment.
P3: Diagnostics can alternate the displayed account/provider metadata when auth.json contains multiple usable credentials because this selects the first entry from an unordered Go map. Sorting provider IDs before selection, or omitting single-account metadata for a multi-provider store, would make status stable.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At services/tuttid/service/agentstatus/opencode_auth.go, line 29:
<comment>Diagnostics can alternate the displayed account/provider metadata when `auth.json` contains multiple usable credentials because this selects the first entry from an unordered Go map. Sorting provider IDs before selection, or omitting single-account metadata for a multi-provider store, would make status stable.</comment>
<file context>
@@ -0,0 +1,57 @@
+ if err := json.Unmarshal(raw, &payload); err != nil || payload == nil {
+ return AuthInfo{}, false
+ }
+ for providerID, rawRecord := range payload {
+ var record openCodeAuthRecord
+ if err := json.Unmarshal(rawRecord, &record); err != nil {
</file context>
| SourceRootDir: filepath.Join(tuttitypes.DefaultStateDir(), "agent-prompt-assets"), | ||
| } | ||
| agentSessionService.RuntimePreparer = agentRuntimePreparer | ||
| agentSessionService.ProviderStatusLister = &agentStatusService |
There was a problem hiding this comment.
P3: In the current wiring, both ProviderStatusLister and AvailabilityChecker are assigned the same agentStatusService. Because AvailabilityChecker is non-nil, ListProviderAvailability always goes through it and never exercises the ProviderStatusLister fallback added in provider_availability.go. The ProviderStatusLister path is dormant in production. Consider whether both are needed, or if one of the two wiring lines is sufficient for the current setup.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At services/tuttid/wiring.go, line 316:
<comment>In the current wiring, both `ProviderStatusLister` and `AvailabilityChecker` are assigned the same `agentStatusService`. Because `AvailabilityChecker` is non-nil, `ListProviderAvailability` always goes through it and never exercises the `ProviderStatusLister` fallback added in `provider_availability.go`. The `ProviderStatusLister` path is dormant in production. Consider whether both are needed, or if one of the two wiring lines is sufficient for the current setup.</comment>
<file context>
@@ -313,6 +313,7 @@ func buildDaemonAPI(ctx context.Context, store workspacedata.CatalogStore, analy
SourceRootDir: filepath.Join(tuttitypes.DefaultStateDir(), "agent-prompt-assets"),
}
agentSessionService.RuntimePreparer = agentRuntimePreparer
+ agentSessionService.ProviderStatusLister = &agentStatusService
agentSessionService.AvailabilityChecker = agentservice.AgentStatusProviderAvailabilityChecker{
Service: &agentStatusService,
</file context>


Summary
Unify the desktop Agent environment diagnostics into one provider-tabbed panel, centralize provider status/action projection, move provider-specific detection into
tuttid, and harden the remaining detection edge cases.Phase 1 — unified provider panel
openAgentEnvPanel({ provider, focus })unchanged; explicit opens select that provider and generic manage opens use the last/default visible tab.UnderlineTabsandStatusDot; keep Cursor/OpenCode preference filtering.includeNetworkonly for the active tab. Badge loading remains local-only; a request-sequence selection guard prevents an initial tab from briefly attaching before the requested/default tab resolves.packages/agent/gui/shared/agentEnv; dock status/actions are presentation-only adapters.DesktopAgentProviderManageDialog, its model, and duplicated inference tests; migrate durable cases to the shared projection tests.Phase 2 — complete provider diagnostics
manualInstallCommandto the daemon status contract and generate Go/TS clients. Provider registry install metadata is now the source of repair commands, including Tutti Agent.tuttid, with install-in-flight skipping and a bounded observation window.Phase 3 — detection hardening (#1–#8)
CODEX_VERSION_UNKNOWN/codex_version_unparseableinstead of silently passing the version floor.386mapping and move Tutti Agent's package requirement to 0.0.3.Normalize("tutti")totutti-agentinstead of unsupportednexight.agentstatus.Service{}fallback.The hard-coded value evidence and compatibility decisions are recorded in
docs/specs/2026-07-11-agent-status-detection-audit.md. In particular, npm publishes Tutti Agent wrapper/platform package 0.0.3 while its current native Darwin ARM64 binary still prints0.0.2; the compatibility gate correctly uses the package manifest.Upstream references checked:
Deferred
Validation
pnpm check:changed --tail-lines 80— 13/13 lanes passedpnpm check:fullpnpm --filter @tutti-os/agent-gui typecheckpnpm --filter @tutti-os/desktop typecheckpnpm --filter @tutti-os/desktop buildpnpm check:api-generatedpnpm check:i18ngo test ./service/agentstatus ./service/agent ./biz/agentproviderNote
Medium Risk
Broad changes across desktop UX, provider status reconciliation, and daemon auth/detection logic; incorrect availability or auth labeling would affect dock, wizard, and session gates, though behavior is heavily test-covered.
Overview
Agent environment management is consolidated into a single tabbed
AgentEnvPanel: the separate manage dialog and its row model are removed, andopenAgentEnvPanel/ workbench agent-manage now open that panel. Tabs show per-provider status badges from a local snapshot; only the active tab runs the deep setup wizard and forces a network-enabledrefreshso cached local-only loads cannot skip deep detection.Shared status semantics move into
projectAgentEnvProviderin@tutti-os/agent-gui/agent-env. Dock hover actions and disabled reasons now follow that projection (e.g. login vs Connect for auth-required providers). Hermes and OpenClaw stay visible but are marked temporarily unsupported and cannot attach the setup wizard.Desktop status service stops second-guessing the daemon: the Cursor renderer runtime probe fallback and transient auth downgrade stabilization are deleted; reconciliation keeps daemon verdicts and only preserves the last
networkfield when local polls omit it. The env wizard always refreshes the active provider on attach (noensureLoadedpath when focus is absent).Daemon / API: provider status gains
manualInstallCommandfrom the registry (wizard repair commands no longer live in the frontend). Detection hardening includes Cursor ACP launch probing duringList(skipped while install is in flight), stricter Codex/OpenCode/Cursor auth marker parsing, unparseable Codex versions ascodex_version_unparseable, longer adapter startup observation, Tutti Agent floor 0.0.3,Normalize("tutti")→ tutti-agent, and injected provider status lister for availability (no empty status service). Runtime auth invalidation can clear when the provider auth command reports authenticated even without credential mtime changes.Architecture/docs and a detection audit note record that read/detect API split remains deferred.
Reviewed by Cursor Bugbot for commit 9d530f4. Bugbot is set up for automated code reviews on this repo. Configure here.