Skip to content

feat(agent-env): unify provider diagnostics and harden detection#1049

Open
devRickyyy wants to merge 3 commits into
mainfrom
feat/unified-agent-env-diagnostics
Open

feat(agent-env): unify provider diagnostics and harden detection#1049
devRickyyy wants to merge 3 commits into
mainfrom
feat/unified-agent-env-diagnostics

Conversation

@devRickyyy

@devRickyyy devRickyyy commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

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

  • Replace the parallel manage table with the existing deep setup wizard hosted inside provider tabs.
  • Keep openAgentEnvPanel({ provider, focus }) unchanged; explicit opens select that provider and generic manage opens use the last/default visible tab.
  • Reuse UI System UnderlineTabs and StatusDot; keep Cursor/OpenCode preference filtering.
  • Attach the wizard and request includeNetwork only 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.
  • Move status/action projection to packages/agent/gui/shared/agentEnv; dock status/actions are presentation-only adapters.
  • Remove DesktopAgentProviderManageDialog, its model, and duplicated inference tests; migrate durable cases to the shared projection tests.

Phase 2 — complete provider diagnostics

  • Add manualInstallCommand to the daemon status contract and generate Go/TS clients. Provider registry install metadata is now the source of repair commands, including Tutti Agent.
  • Show Hermes and OpenClaw as explicitly temporarily unsupported without attaching a wizard or probing them.
  • Move the Cursor ACP runtime launch probe from the renderer into tuttid, with install-in-flight skipping and a bounded observation window.
  • Add provider-specific Cursor and OpenCode auth marker parsing so marker presence alone is not treated as authenticated.
  • Remove the renderer Cursor probe fallback and reconciliation patch.

Phase 3 — detection hardening (#1#8)

  1. Validate Codex auth marker contents and make unknown future marker formats conservative.
  2. Report unparseable Codex versions as CODEX_VERSION_UNKNOWN / codex_version_unparseable instead of silently passing the version floor.
  3. Extend the default adapter startup observation from 600 ms to 1 s and cover a 700 ms delayed crash.
  4. Audit platform mappings, endpoints, mirrors, and version floors. Remove dead Codex 386 mapping and move Tutti Agent's package requirement to 0.0.3.
  5. Clear a runtime auth invalidation when the provider auth command confirms authentication, even when credential mtime does not change.
  6. Correct Normalize("tutti") to tutti-agent instead of unsupported nexight.
  7. Inject the configured provider status lister into agent availability; remove the empty agentstatus.Service{} fallback.
  8. Remove the renderer auth downgrade debounce so every consumer uses the daemon verdict. The remaining network merge only preserves an optional field omitted by local-only reads.

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 prints 0.0.2; the compatibility gate correctly uses the package manifest.

Upstream references checked:

Deferred

  • Phase 3 docs(readme): optimize illustration file sizes #9, the read/detect API split, remains deferred. The existing design note now records the current gap and no longer assumes an empty status service instance. Implementing it still requires a daemon store plus API/client/polling migration and is intentionally kept out of this correctness patch.

Validation

  • pnpm check:changed --tail-lines 80 — 13/13 lanes passed
  • pre-push pnpm check:full
  • AgentGUI: 127 files / 2,217 tests passed
  • Desktop: 1,231 tests passed (1,229 passed, 2 skipped)
  • pnpm --filter @tutti-os/agent-gui typecheck
  • pnpm --filter @tutti-os/desktop typecheck
  • pnpm --filter @tutti-os/desktop build
  • pnpm check:api-generated
  • pnpm check:i18n
  • UI, renderer, Agent Activity runtime boundary checks
  • go test ./service/agentstatus ./service/agent ./biz/agentprovider
  • Isolated production build launched with a dedicated state/user-data directory; daemon reported Codex ready at 0.144.1 and exercised the Tutti Agent 0.0.3 repair path. Visual tab switching and Claude login were not completed because the shared macOS desktop was occupied by an unrelated recording/editor session; no existing login credentials were changed.

Note

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, and openAgentEnvPanel / 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-enabled refresh so cached local-only loads cannot skip deep detection.

Shared status semantics move into projectAgentEnvProvider in @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 network field when local polls omit it. The env wizard always refreshes the active provider on attach (no ensureLoaded path when focus is absent).

Daemon / API: provider status gains manualInstallCommand from the registry (wizard repair commands no longer live in the frontend). Detection hardening includes Cursor ACP launch probing during List (skipped while install is in flight), stricter Codex/OpenCode/Cursor auth marker parsing, unparseable Codex versions as codex_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.

Signed-off-by: rick <contactchienlin@gmail.com>
Signed-off-by: rick <contactchienlin@gmail.com>
Signed-off-by: rick <contactchienlin@gmail.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ 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
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9d530f4. Configure here.

export function buildAgentEnvWizardViewModel(
input: AgentEnvWizardViewModelInput
): AgentEnvWizardViewModel {
const { status, activeAction, provider } = input;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9d530f4. Configure here.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

@cubic-dev-ai cubic-dev-ai Bot Jul 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

value={provider}
onValueChange={(nextProvider) => {
lastSelectedProviderRef.current = nextProvider;
setSelection({

@cubic-dev-ai cubic-dev-ai Bot Jul 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

// 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 });

@cubic-dev-ai cubic-dev-ai Bot Jul 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

return true
}
return spec.Provider == agentprovider.Codex && s.executableFile(runtimeResolution.AdapterPath)
return (spec.Provider == agentprovider.Codex || spec.Provider == agentprovider.Cursor) &&

@cubic-dev-ai cubic-dev-ai Bot Jul 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

availability.Status = AvailabilityNotInstalled
availability.ReasonCode = codexReasonCodeFromErrorCode(string(CodexErrPlatformPkgIncomplete))
actions = append(actions, daemonAction(ActionInstall))
} else if spec.Provider == agentprovider.Codex && !codexVersionParseable(cliVersion) {

@cubic-dev-ai cubic-dev-ai Bot Jul 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

case "api":
return strings.TrimSpace(record.Key) != ""
case "oauth":
return strings.TrimSpace(record.Access) != "" || strings.TrimSpace(record.Refresh) != ""

@cubic-dev-ai cubic-dev-ai Bot Jul 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

actions = append(actions, daemonAction(ActionInstall))
} else if spec.Provider == agentprovider.Codex && !codexVersionParseable(cliVersion) {
availability.Status = AvailabilityUnknown
availability.ReasonCode = "codex_version_unparseable"

@cubic-dev-ai cubic-dev-ai Bot Jul 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

<span>{resolveProviderLabel(candidate)}</span>
<StatusDot
ariaLabel={statusLabel}
pulse={projection.status === "checking"}

@cubic-dev-ai cubic-dev-ai Bot Jul 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
pulse={projection.status === "checking"}
pulse={
projection.pending || projection.status === "checking"
}
Fix with cubic

if err := json.Unmarshal(raw, &payload); err != nil || payload == nil {
return AuthInfo{}, false
}
for providerID, rawRecord := range payload {

@cubic-dev-ai cubic-dev-ai Bot Jul 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

Comment thread services/tuttid/wiring.go
SourceRootDir: filepath.Join(tuttitypes.DefaultStateDir(), "agent-prompt-assets"),
}
agentSessionService.RuntimePreparer = agentRuntimePreparer
agentSessionService.ProviderStatusLister = &agentStatusService

@cubic-dev-ai cubic-dev-ai Bot Jul 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant