sneakoscope 7.3.0 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -10
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/codex-app-command.js +1 -1
- package/dist/cli/command-manifest-lite.js +4 -1
- package/dist/cli/command-registry.js +2 -0
- package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
- package/dist/cli/install-helpers-codex-lb-config.js +114 -9
- package/dist/cli/install-helpers-install-support.js +6 -16
- package/dist/cli/install-helpers.js +788 -600
- package/dist/cli/router.js +30 -2
- package/dist/commands/codex-app.js +37 -1
- package/dist/commands/codex-lb.js +491 -300
- package/dist/config/skills-manifest.json +57 -71
- package/dist/core/align/align-route.js +127 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
- package/dist/core/codex-app/codex-app-restart.js +83 -0
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
- package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
- package/dist/core/codex-app/menubar/constants.js +5 -0
- package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
- package/dist/core/codex-app/menubar/installer.js +18 -0
- package/dist/core/codex-app/multi-provider-router.js +22 -10
- package/dist/core/codex-app/openrouter-activate.js +26 -1
- package/dist/core/codex-app.js +118 -26
- package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
- package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
- package/dist/core/codex-control/codex-task-runner.js +2 -2
- package/dist/core/codex-lb/capability-runner.js +204 -0
- package/dist/core/codex-lb/capability-types.js +1 -0
- package/dist/core/codex-lb/codex-lb-env.js +9 -13
- package/dist/core/codex-lb/codex-lb-setup.js +45 -8
- package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
- package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
- package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
- package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
- package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
- package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
- package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
- package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
- package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
- package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
- package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
- package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
- package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
- package/dist/core/codex-lb/desktop-controller.js +1410 -0
- package/dist/core/codex-lb/desktop-mode.js +32 -0
- package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
- package/dist/core/codex-lb/desktop-service.js +613 -0
- package/dist/core/codex-lb/legacy-migration.js +575 -0
- package/dist/core/codex-lb/migration-receipt.js +246 -0
- package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
- package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
- package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
- package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
- package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
- package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
- package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
- package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
- package/dist/core/codex-native/core-skill-manifest.js +22 -6
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
- package/dist/core/commands/align-command.js +220 -0
- package/dist/core/commands/image-ux-review-command.js +280 -68
- package/dist/core/commands/naruto-command.js +257 -8
- package/dist/core/commands/ppt-command.js +2 -2
- package/dist/core/commands/telegram-command.js +402 -34
- package/dist/core/commands/triwiki-command.js +11 -1
- package/dist/core/commands/triwiki-graph-command.js +160 -0
- package/dist/core/commands/wiki-command.js +100 -20
- package/dist/core/doctor/skill-legacy-surface.js +1 -3
- package/dist/core/errors/message.js +3 -0
- package/dist/core/evidence/evidence-freshness.js +14 -0
- package/dist/core/feature-fixtures.js +9 -1
- package/dist/core/feature-registry.js +5 -3
- package/dist/core/fsx.js +7 -3
- package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
- package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
- package/dist/core/hooks-runtime/hook-context.js +15 -2
- package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
- package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
- package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
- package/dist/core/hooks-runtime.js +33 -89
- package/dist/core/image-ux-review/callout-extraction.js +6 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +64 -23
- package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
- package/dist/core/image-ux-review.js +230 -12
- package/dist/core/imagegen/codex-lb-imagegen-target.js +21 -7
- package/dist/core/imagegen/imagegen-capability.js +10 -4
- package/dist/core/imagegen/require-imagegen.js +29 -5
- package/dist/core/init/skills.js +25 -13
- package/dist/core/init.js +1 -1
- package/dist/core/install/installed-package-smoke.js +157 -4
- package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
- package/dist/core/managed-path-safety.js +8 -10
- package/dist/core/mcp-config/backup.js +2 -4
- package/dist/core/mcp-config/config-reader.js +2 -4
- package/dist/core/mcp-config/scope.js +3 -5
- package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
- package/dist/core/naruto/context-graph-advisor.js +0 -0
- package/dist/core/perf/release-latency-slo.js +12 -6
- package/dist/core/pipeline-internals/runtime-core.js +78 -0
- package/dist/core/pipeline-internals/runtime-gates.js +5 -2
- package/dist/core/policy/latest-version-guidance.js +113 -0
- package/dist/core/ppt.js +8 -8
- package/dist/core/preflight/parallel-preflight-engine.js +1 -1
- package/dist/core/proof/auto-finalize.js +98 -6
- package/dist/core/proof/route-proof-gate.js +12 -1
- package/dist/core/proof/route-proof-policy.js +8 -8
- package/dist/core/release/gate-manifest.js +32 -1
- package/dist/core/release/npm-stage-contract.js +21 -0
- package/dist/core/release/package-size-budget.js +2 -2
- package/dist/core/release/release-gate-contract.js +5 -0
- package/dist/core/release/release-proof-io.js +1 -3
- package/dist/core/release/release-real-contract.js +5 -4
- package/dist/core/release/release-stamp-proof.js +1 -3
- package/dist/core/release/stage-publish.js +27 -0
- package/dist/core/remote/audit-idempotency.js +50 -18
- package/dist/core/remote/codex-public-activity.js +283 -0
- package/dist/core/remote/index.js +1 -0
- package/dist/core/remote/ssh-worker-client.js +4 -1
- package/dist/core/remote/worker.js +206 -27
- package/dist/core/research/research-adversarial-review.js +1 -3
- package/dist/core/research/research-claim-synthesizer.js +1 -3
- package/dist/core/research/research-falsification-runner.js +1 -3
- package/dist/core/research/research-source-evidence.js +1 -3
- package/dist/core/responses-stream.js +65 -19
- package/dist/core/routes/constants.js +1 -1
- package/dist/core/routes/design-policy.js +7 -2
- package/dist/core/routes/dollar-manifest-lite.js +2 -4
- package/dist/core/routes/evidence.js +1 -1
- package/dist/core/routes.js +30 -10
- package/dist/core/safety/command-contract/contracts.js +2 -0
- package/dist/core/safety/command-contract/types.js +1 -1
- package/dist/core/search/context-graph-seeds.js +205 -0
- package/dist/core/search/context.js +0 -0
- package/dist/core/search/provider.js +2 -2
- package/dist/core/secret-redaction.js +6 -0
- package/dist/core/subagents/naruto-help-contract.js +2 -1
- package/dist/core/subagents/naruto-host-credentials.js +161 -0
- package/dist/core/subagents/official-subagent-preparation.js +4 -1
- package/dist/core/subagents/official-subagent-prompt.js +44 -2
- package/dist/core/subagents/official-subagent-runner.js +26 -8
- package/dist/core/subagents/triwiki-attention.js +135 -123
- package/dist/core/telegram/bot-api.js +85 -10
- package/dist/core/telegram/center-snapshot.js +718 -0
- package/dist/core/telegram/config.js +158 -31
- package/dist/core/telegram/hub.js +288 -33
- package/dist/core/telegram/index.js +2 -0
- package/dist/core/telegram/ingress-spool.js +419 -0
- package/dist/core/telegram/ledgers.js +188 -29
- package/dist/core/telegram/messages.js +26 -6
- package/dist/core/telegram/owner-lock.js +10 -7
- package/dist/core/telegram/runtime-projection.js +38 -3
- package/dist/core/telegram/runtime.js +298 -63
- package/dist/core/telegram/service.js +137 -8
- package/dist/core/telegram/setup.js +291 -73
- package/dist/core/text/strings.js +3 -0
- package/dist/core/triwiki/code-pack.js +12 -120
- package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
- package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
- package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
- package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
- package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
- package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
- package/dist/core/triwiki/context-graph/contracts.js +236 -0
- package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
- package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
- package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
- package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
- package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
- package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
- package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
- package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
- package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
- package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
- package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
- package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
- package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
- package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
- package/dist/core/triwiki/context-graph/graph-index.js +143 -0
- package/dist/core/triwiki/context-graph/ids.js +0 -0
- package/dist/core/triwiki/context-graph/lint/index.js +0 -0
- package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
- package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
- package/dist/core/triwiki/context-graph/paths.js +116 -0
- package/dist/core/triwiki/context-graph/profiles.js +78 -0
- package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
- package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
- package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
- package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
- package/dist/core/triwiki/context-graph/projections/index.js +7 -0
- package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
- package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
- package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
- package/dist/core/triwiki/context-graph/query/explain.js +0 -0
- package/dist/core/triwiki/context-graph/query/index.js +178 -0
- package/dist/core/triwiki/context-graph/query/load.js +116 -0
- package/dist/core/triwiki/context-graph/query/pack.js +117 -0
- package/dist/core/triwiki/context-graph/query/rank.js +142 -0
- package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
- package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
- package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
- package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
- package/dist/core/triwiki/context-graph/query-types.js +29 -0
- package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
- package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
- package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
- package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
- package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
- package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
- package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
- package/dist/core/verification/context-graph-affected.js +271 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
- package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
- package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
- package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
- package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
- package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
- package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
- package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
- package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
- package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
- package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
- package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
- package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
- package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
- package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
- package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
- package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
- package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
- package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
- package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
- package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
- package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
- package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
- package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
- package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
- package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
- package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
- package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
- package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
- package/dist/scripts/context-graph-check.js +191 -0
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
- package/dist/scripts/gate-policy-audit-check.js +1 -1
- package/dist/scripts/helper-dedup-check.js +0 -2
- package/dist/scripts/installed-package-smoke-check.js +4 -1
- package/dist/scripts/latest-version-guidance-check.js +74 -0
- package/dist/scripts/release-metadata-check.js +21 -4
- package/dist/scripts/release-real-check.js +7 -0
- package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
- package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
- package/package.json +8 -3
- package/release-gates.v2.json +176 -1
- package/runtime-required-scripts.json +30 -0
- package/schemas/triwiki/context-graph.schema.json +119 -0
- package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
- package/dist/cli/install-helpers-codex-lb-selftest.js +0 -481
- package/dist/core/triwiki/code-index-scanner.js +0 -309
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import Cocoa
|
|
2
|
-
|
|
3
2
|
final class ProvidersViewController: NSViewController, ControlCenterPage, NSTextFieldDelegate {
|
|
4
|
-
private
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
3
|
+
private let capabilityDefinitions: [(label: String, keys: [String])] = [
|
|
4
|
+
("OAuth Identity", ["oauth_identity", "provider_identity"]), ("Built-in Provider", ["built_in_provider", "provider_identity"]),
|
|
5
|
+
("Bridge", ["bridge"]), ("Models", ["models", "model_picker", "catalog"]), ("Fast", ["fast", "fast_mode"]),
|
|
6
|
+
("Image", ["image", "image_generation"]), ("Computer", ["computer", "computer_use"]), ("Browser", ["browser", "browser_use"]),
|
|
7
|
+
("Voice", ["voice", "voice_mode"]), ("Plugins", ["plugins"])
|
|
8
|
+
]
|
|
9
|
+
private let capabilityStates = Set(["verified", "available_unverified", "blocked", "unsupported", "skipped"])
|
|
10
|
+
private let cliLaunchCommand = "codex --config model_provider='\"codex-lb\"'"
|
|
15
11
|
let processClient: ProcessClient
|
|
16
12
|
let operations: OperationCoordinator
|
|
17
|
-
let providerStatus = NativeView.detail("
|
|
13
|
+
let providerStatus = NativeView.detail("Desktop routing status unchecked.")
|
|
14
|
+
let cliProviderStatus = NativeView.detail("CLI provider status unchecked.")
|
|
15
|
+
let capabilityStatus = NativeView.detail("Capabilities have not been verified.")
|
|
18
16
|
let openRouterCredentialStatus = NativeView.detail("Credential: checking…")
|
|
19
17
|
let openRouterActiveStatus = NativeView.detail("Active provider: checking…")
|
|
20
18
|
let openRouterCatalogStatus = NativeView.detail("Model catalog has not loaded yet.")
|
|
@@ -30,9 +28,12 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
|
|
|
30
28
|
let multiProvider = MultiProviderRouterControls()
|
|
31
29
|
let roleStatus = NativeView.detail("Role model settings are loading…")
|
|
32
30
|
let globalSpinner = NativeView.spinner(label: "Provider operation in progress")
|
|
33
|
-
let fastStatus = NativeView.detail("Fast
|
|
31
|
+
let fastStatus = NativeView.detail("Codex Fast: checking the official service-tier setting…")
|
|
34
32
|
let activeProviderBadge = ControlKit.badge("Checking the active Codex provider…", tone: .busy)
|
|
33
|
+
var capabilityRows: [String: NSTextField] = [:]
|
|
34
|
+
var desktopFullRoutingNow = false
|
|
35
35
|
var codexLbSelectedNow = false
|
|
36
|
+
var chatgptOauthPresentNow = false
|
|
36
37
|
var openRouterSelectedNow = false
|
|
37
38
|
var openRouterActiveModel = ""
|
|
38
39
|
var routerSelectedNow = false
|
|
@@ -51,47 +52,59 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
|
|
|
51
52
|
var openRouterModelSelectionPending = false
|
|
52
53
|
var openRouterActionRan = false
|
|
53
54
|
var busy = false
|
|
54
|
-
|
|
55
55
|
init(processClient: ProcessClient, operations: OperationCoordinator) {
|
|
56
56
|
self.processClient = processClient
|
|
57
57
|
self.operations = operations
|
|
58
58
|
super.init(nibName: nil, bundle: nil)
|
|
59
59
|
}
|
|
60
60
|
required init?(coder: NSCoder) { nil }
|
|
61
|
-
|
|
62
61
|
override func loadView() {
|
|
63
|
-
let
|
|
64
|
-
let
|
|
65
|
-
let
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
let
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
62
|
+
let enableDesktop = ControlKit.primaryButton("Use Codex LB", target: self, action: #selector(enableDesktopFull))
|
|
63
|
+
let verifyDesktop = NativeView.button("Verify Capabilities", target: self, action: #selector(verifyDesktopCapabilities))
|
|
64
|
+
let disableDesktop = NativeView.button("Use ChatGPT OAuth Only", target: self, action: #selector(disableDesktopRouting))
|
|
65
|
+
enableDesktop.setAccessibilityLabel("Use Codex LB Desktop Full Capability routing while keeping ChatGPT OAuth, then restart Codex App")
|
|
66
|
+
verifyDesktop.setAccessibilityLabel("Verify Desktop transport capabilities")
|
|
67
|
+
disableDesktop.setAccessibilityLabel("Disable Codex LB Desktop routing and keep ordinary ChatGPT OAuth, then restart Codex App")
|
|
68
|
+
// Keep the mode CTAs discoverable: never hide Use Codex LB / Use ChatGPT OAuth Only based on status parsing.
|
|
69
|
+
let configureCli = NativeView.button("Configure / Update…", target: self, action: #selector(setDomainAndKey))
|
|
70
|
+
let useCli = NativeView.button("Use Saved CLI Provider", target: self, action: #selector(useCliProvider))
|
|
71
|
+
let testCli = NativeView.button("Test", target: self, action: #selector(testConnection))
|
|
72
|
+
let copyCli = NativeView.button("Copy CLI Command", target: self, action: #selector(copyCliCommand))
|
|
73
|
+
let fastOn = NativeView.button("Codex Fast On", target: self, action: #selector(fastOn))
|
|
74
|
+
let fastOff = NativeView.button("Codex Fast Off", target: self, action: #selector(fastOff))
|
|
75
|
+
actionButtons = [enableDesktop, verifyDesktop, disableDesktop, configureCli, useCli, testCli, copyCli, fastOn, fastOff]
|
|
76
|
+
let desktop = NativeView.card(
|
|
77
|
+
title: "Codex LB · Desktop Full Capability (Recommended)",
|
|
78
|
+
subtitle: "Selectable Codex LB mode: keeps ChatGPT OAuth identity and the built-in OpenAI provider while routing model traffic through the local codex-lb bridge. Use ChatGPT OAuth Only removes LB routing without touching login.",
|
|
79
|
+
views: [providerStatus, ControlKit.actionRow([enableDesktop, verifyDesktop], trailing: [disableDesktop])]
|
|
80
|
+
)
|
|
81
|
+
let cli = NativeView.card(
|
|
82
|
+
title: "Codex LB · Credentials & CLI Provider",
|
|
83
|
+
subtitle: "Saves the official Center Codex LB host and API key (sks-codex-lb store). Desktop routing buttons above consume that same store automatically — no shell source or twin env files.",
|
|
84
|
+
views: [
|
|
85
|
+
cliProviderStatus,
|
|
86
|
+
NativeView.detail("One-off command: \(cliLaunchCommand)"),
|
|
87
|
+
ControlKit.actionRow([configureCli, useCli]),
|
|
88
|
+
ControlKit.actionRow([testCli, copyCli])
|
|
89
|
+
]
|
|
78
90
|
)
|
|
79
91
|
let fast = NativeView.card(
|
|
80
|
-
title: "Fast
|
|
81
|
-
subtitle: "
|
|
82
|
-
views: [
|
|
92
|
+
title: "Codex Fast",
|
|
93
|
+
subtitle: "Official Codex speed option: 1.5× faster on supported models. It changes the service tier, not the selected model, Codex-Spark, or reasoning effort.",
|
|
94
|
+
views: [
|
|
95
|
+
fastStatus,
|
|
96
|
+
NativeView.detail("ChatGPT sign-in: GPT-5.6 and GPT-5.5 use credits at 2.5× Standard; GPT-5.4 uses 2× Standard. API-key Codex uses API token pricing instead, and API Priority processing is a separate billing path."),
|
|
97
|
+
NativeView.row([fastOn, fastOff])
|
|
98
|
+
]
|
|
83
99
|
)
|
|
84
100
|
view = NativeView.page([
|
|
85
101
|
NativeView.row([NativeView.title("Providers & Models"), globalSpinner]),
|
|
86
|
-
NativeView.detail("
|
|
87
|
-
makeActiveProviderCard(),
|
|
88
|
-
|
|
102
|
+
NativeView.detail("Codex LB and ChatGPT OAuth are explicit Desktop modes below. CLI automation and Fast stay independent. Secrets are sent through stdin and redacted from logs."),
|
|
103
|
+
makeActiveProviderCard(), desktop, cli, makeCapabilityMatrixCard(), makeOpenRouterCard(),
|
|
104
|
+
makeMultiProviderRouterCard(), makeRoleModelsCard(), fast
|
|
89
105
|
])
|
|
90
|
-
refresh()
|
|
91
106
|
}
|
|
92
|
-
|
|
93
107
|
func refreshOnAppear() { refresh() }
|
|
94
|
-
|
|
95
108
|
func setBusy(_ value: Bool) {
|
|
96
109
|
busy = value
|
|
97
110
|
for button in actionButtons { button.isEnabled = !value }
|
|
@@ -105,47 +118,41 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
|
|
|
105
118
|
if value { globalSpinner.startAnimation(nil) }
|
|
106
119
|
else { globalSpinner.stopAnimation(nil) }
|
|
107
120
|
}
|
|
108
|
-
|
|
109
121
|
private func run(_ args: [String], title: String, kind: String, group: String?, timeout: TimeInterval = NativeView.mutationTimeout, completion: (() -> Void)? = nil) {
|
|
110
|
-
guard !busy else {
|
|
111
|
-
providerStatus.stringValue = "Another provider action is already running."
|
|
112
|
-
return
|
|
113
|
-
}
|
|
122
|
+
guard !busy else { fastStatus.stringValue = "Another provider action is already running."; return }
|
|
114
123
|
guard let snapshot = operations.begin(kind: kind, mutationGroup: group, summary: title) else {
|
|
115
|
-
|
|
124
|
+
fastStatus.stringValue = "Another guarded mutation is already running. Wait or open Diagnostics."
|
|
116
125
|
return
|
|
117
126
|
}
|
|
118
|
-
setBusy(true)
|
|
119
|
-
providerStatus.stringValue = "\(title)…"
|
|
127
|
+
setBusy(true); fastStatus.stringValue = "\(title)…"
|
|
120
128
|
_ = operations.update(snapshot, state: .running, stage: "running", progress: nil, summary: title)
|
|
121
129
|
processClient.run(args, timeout: timeout) { [weak self] result in
|
|
122
130
|
guard let self = self else { return }
|
|
123
131
|
self.setBusy(false)
|
|
124
|
-
_ = self.operations.update(
|
|
125
|
-
|
|
126
|
-
state: result.code == 0 ? .succeeded : .failed,
|
|
127
|
-
stage: "complete",
|
|
128
|
-
progress: 1,
|
|
129
|
-
summary: result.code == 0 ? "\(title) completed" : "\(title) failed"
|
|
130
|
-
)
|
|
131
|
-
if result.code != 0 {
|
|
132
|
-
self.providerStatus.stringValue = "\(title) failed · \(NativeView.redactPreview(result.output))"
|
|
133
|
-
}
|
|
134
|
-
self.refresh()
|
|
132
|
+
_ = self.operations.update(snapshot, state: result.code == 0 ? .succeeded : .failed, stage: "complete", progress: 1, summary: result.code == 0 ? "\(title) completed" : "\(title) failed")
|
|
133
|
+
if result.code != 0 { self.fastStatus.stringValue = "\(title) failed · \(NativeView.redactPreview(result.output))" }
|
|
135
134
|
completion?()
|
|
136
135
|
}
|
|
137
136
|
}
|
|
138
|
-
|
|
139
137
|
func refresh() {
|
|
140
138
|
processClient.run(["codex-lb", "status", "--json"], timeout: NativeView.statusTimeout) { [weak self] result in
|
|
141
139
|
guard let self = self else { return }
|
|
142
|
-
guard
|
|
143
|
-
if !self.busy {
|
|
140
|
+
guard let json = self.json(result.output) else {
|
|
141
|
+
if !self.busy {
|
|
142
|
+
self.providerStatus.stringValue = "Desktop routing status unavailable. No routing mode was assumed."
|
|
143
|
+
self.cliProviderStatus.stringValue = "CLI provider status unavailable. No configuration was assumed."
|
|
144
|
+
}
|
|
144
145
|
return
|
|
145
146
|
}
|
|
146
|
-
|
|
147
|
+
let snapshot = ProviderRoutingTruth.snapshot(from: json)
|
|
148
|
+
self.desktopFullRoutingNow = snapshot.desktopFullRouting
|
|
149
|
+
self.codexLbSelectedNow = snapshot.legacyCodexLbSelected
|
|
150
|
+
self.chatgptOauthPresentNow = snapshot.chatgptOauthPresent
|
|
147
151
|
self.renderActiveProviderSummary()
|
|
148
|
-
if !self.busy {
|
|
152
|
+
if !self.busy {
|
|
153
|
+
self.providerStatus.stringValue = self.describeDesktopStatus(snapshot)
|
|
154
|
+
self.cliProviderStatus.stringValue = self.describeCliStatus(snapshot)
|
|
155
|
+
}
|
|
149
156
|
}
|
|
150
157
|
refreshOpenRouterStatus()
|
|
151
158
|
refreshMultiProviderRouterStatus()
|
|
@@ -153,223 +160,130 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
|
|
|
153
160
|
refreshRoleModels()
|
|
154
161
|
refreshFastStatus()
|
|
155
162
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
let keyInShared = codexLb["codex_lb_key_in_shared_auth"] as? Bool == true
|
|
164
|
-
|
|
165
|
-
if !configured {
|
|
166
|
-
return "Codex LB: not configured. Use Set Domain and Key, then Use codex-lb."
|
|
163
|
+
private func makeCapabilityMatrixCard() -> NSBox {
|
|
164
|
+
var views: [NSView] = [capabilityStatus]
|
|
165
|
+
for definition in capabilityDefinitions {
|
|
166
|
+
let value = NativeView.detail("skipped")
|
|
167
|
+
value.setAccessibilityLabel("\(definition.label) capability state")
|
|
168
|
+
capabilityRows[definition.label] = value
|
|
169
|
+
views.append(ControlKit.keyValueRow(definition.label, value))
|
|
167
170
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if
|
|
172
|
-
return "Codex LB
|
|
171
|
+
return NativeView.card(title: "Capability Matrix", subtitle: "States come from capability evidence, not configuration alone: verified, available_unverified, blocked, unsupported, or skipped.", views: views)
|
|
172
|
+
}
|
|
173
|
+
private func describeDesktopStatus(_ snapshot: ProviderRoutingTruth.Snapshot) -> String {
|
|
174
|
+
if snapshot.legacyDestructive {
|
|
175
|
+
return "Legacy destructive provider/auth state detected · choose Use Codex LB for migration guidance, or Use ChatGPT OAuth Only to remove SKS-owned routing."
|
|
173
176
|
}
|
|
174
|
-
if
|
|
175
|
-
|
|
177
|
+
if snapshot.mode == "desktop-native-bridge" {
|
|
178
|
+
let oauth = snapshot.chatgptOauthPresent
|
|
179
|
+
? "ChatGPT OAuth present"
|
|
180
|
+
: "ChatGPT OAuth missing — sign in with ChatGPT, then Use Codex LB"
|
|
181
|
+
return "Codex LB mode: enabled · \(oauth) · built-in OpenAI via bridge. Run Verify Capabilities for evidence."
|
|
176
182
|
}
|
|
177
|
-
if
|
|
178
|
-
return
|
|
183
|
+
if snapshot.mode == "disabled" {
|
|
184
|
+
return snapshot.chatgptOauthPresent
|
|
185
|
+
? "ChatGPT OAuth mode: active · Codex LB Desktop routing disabled. Choose Use Codex LB to switch."
|
|
186
|
+
: "ChatGPT OAuth mode: selected, but ChatGPT sign-in is missing · run codex login."
|
|
179
187
|
}
|
|
180
|
-
return
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
private func codexLbPayload(_ json: [String: Any]) -> [String: Any] {
|
|
184
|
-
if let codexLb = json["codex_lb"] as? [String: Any] { return codexLb }
|
|
185
|
-
return json
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
private func codexLbReadiness(_ codexLb: [String: Any]) -> CodexLbReadiness {
|
|
189
|
-
let sharedOpenAiRouting = codexLb["shared_openai_routing"] as? [String: Any]
|
|
190
|
-
return CodexLbReadiness(
|
|
191
|
-
selected: codexLb["selected"] as? Bool == true,
|
|
192
|
-
providerReady: codexLb["provider_ready"] as? Bool == true,
|
|
193
|
-
authRoutingCoherent: codexLb["auth_routing_coherent"] as? Bool == true,
|
|
194
|
-
sharedOpenAiRoutingSafe: sharedOpenAiRouting?["safe"] as? Bool == true
|
|
195
|
-
)
|
|
188
|
+
return snapshot.chatgptOauthPresent
|
|
189
|
+
? "ChatGPT OAuth mode: active · Codex LB Desktop routing not enabled. Choose Use Codex LB to switch."
|
|
190
|
+
: "ChatGPT OAuth mode available · ChatGPT sign-in is missing · run codex login, then Use Codex LB if needed."
|
|
196
191
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
let model = modelSelection["model"] as? String
|
|
201
|
-
let source = modelSelection["source"] as? String
|
|
202
|
-
if let model = model, !model.isEmpty, let source = source, !source.isEmpty {
|
|
203
|
-
return "model \(model) via \(source)"
|
|
192
|
+
private func describeCliStatus(_ snapshot: ProviderRoutingTruth.Snapshot) -> String {
|
|
193
|
+
if snapshot.cliCredentialsConfigured && snapshot.cliProviderStored {
|
|
194
|
+
return "CLI provider: configured · availability remains unverified until Test succeeds."
|
|
204
195
|
}
|
|
205
|
-
if
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
private func structuredReason(_ json: [String: Any], codexLb: [String: Any], readiness: CodexLbReadiness) -> String {
|
|
210
|
-
if codexLb["env_key_configured"] as? Bool != true { return "API key is missing" }
|
|
211
|
-
if (codexLb["base_url"] as? String)?.isEmpty != false { return "base URL is missing" }
|
|
212
|
-
if let status = json["status"] as? String,
|
|
213
|
-
["transport_blocked", "first_request_failed", "missing_response_id", "second_request_failed", "previous_response_not_found", "tool_output_recovery_blocked"].contains(status) {
|
|
214
|
-
return status.replacingOccurrences(of: "_", with: " ")
|
|
196
|
+
if snapshot.cliProviderStored {
|
|
197
|
+
return "CLI provider: configured, but credentials are incomplete · choose Configure / Update…"
|
|
215
198
|
}
|
|
216
|
-
|
|
217
|
-
if !readiness.providerReady { return "provider configuration is not ready" }
|
|
218
|
-
if !readiness.authRoutingCoherent { return "provider and authentication routing disagree" }
|
|
219
|
-
if !readiness.sharedOpenAiRoutingSafe { return "shared OpenAI routing guard is unsafe" }
|
|
220
|
-
if let reason = json["reason"] as? String, !reason.isEmpty { return reason.replacingOccurrences(of: "_", with: " ") }
|
|
221
|
-
return "response-chain verification did not pass"
|
|
199
|
+
return "CLI provider: not configured · choose Configure / Update…"
|
|
222
200
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
&& (codexLb["base_url"] as? String)?.isEmpty == false
|
|
227
|
-
if status == "not_configured" {
|
|
228
|
-
return credentialsSaved
|
|
229
|
-
? "click Use codex-lb to activate the saved credentials"
|
|
230
|
-
: "click Set Domain and Key"
|
|
231
|
-
}
|
|
232
|
-
if status == "missing_env_key" || status == "missing_base_url" || !credentialsSaved {
|
|
233
|
-
return "click Set Domain and Key"
|
|
234
|
-
}
|
|
235
|
-
if status == "model_unselected" { return "select a Codex model, then retry" }
|
|
236
|
-
if !readiness.ready { return "click Use codex-lb; if it still fails, open Diagnostics" }
|
|
237
|
-
return "verify the domain, key, and network, then retry"
|
|
201
|
+
private func capabilityPayload(_ json: [String: Any]) -> [String: Any] {
|
|
202
|
+
for key in ["report", "capabilities", "desktop_capabilities", "capability_report"] { if let payload = json[key] as? [String: Any] { return payload } }
|
|
203
|
+
return json
|
|
238
204
|
}
|
|
239
|
-
|
|
240
|
-
private func
|
|
241
|
-
let
|
|
242
|
-
let
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
205
|
+
@discardableResult
|
|
206
|
+
private func renderCapabilityMatrix(_ json: [String: Any]) -> String? {
|
|
207
|
+
let payload = capabilityPayload(json)
|
|
208
|
+
let trustVerified = CapabilityVerificationTruth.deepEvidenceTrusted(in: json)
|
|
209
|
+
var recognized = false
|
|
210
|
+
var rendered: [String] = []
|
|
211
|
+
for definition in capabilityDefinitions {
|
|
212
|
+
let states = definition.keys.compactMap { capabilityState($0, payload: payload, allowVerified: trustVerified) }
|
|
213
|
+
if !states.isEmpty { recognized = true }
|
|
214
|
+
let state = states.max { capabilityStateRank($0) < capabilityStateRank($1) } ?? "skipped"
|
|
215
|
+
rendered.append(state)
|
|
216
|
+
let label = capabilityRows[definition.label]
|
|
217
|
+
label?.stringValue = state
|
|
218
|
+
label?.textColor = capabilityColor(state)
|
|
251
219
|
}
|
|
252
|
-
|
|
253
|
-
let
|
|
254
|
-
|
|
220
|
+
guard recognized else { capabilityStatus.stringValue = "Capability response did not contain recognized evidence. No state was assumed."; return nil }
|
|
221
|
+
let reportedOverall = normalizedCapabilityState(payload["overall"] as? String, source: nil)
|
|
222
|
+
?? normalizedCapabilityState(payload["state"] as? String, source: nil)
|
|
223
|
+
let overall = reportedOverall == "verified" ? (trustVerified ? "verified" : "available_unverified") : (reportedOverall ?? "available_unverified")
|
|
224
|
+
let verifiedCount = rendered.filter { $0 == "verified" }.count
|
|
225
|
+
let blockers = CapabilityVerificationTruth.blockers(in: json)
|
|
226
|
+
let blockerText = blockers.isEmpty ? "" : " · blockers: \(blockers.prefix(3).joined(separator: ", "))"
|
|
227
|
+
let trustText = reportedOverall == "verified" && !trustVerified ? " · trusted deep evidence missing" : ""
|
|
228
|
+
capabilityStatus.stringValue = "Capabilities: \(overall) · \(verifiedCount)/\(rendered.count) verified\(trustText)\(blockerText)."
|
|
229
|
+
return overall
|
|
255
230
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
let
|
|
259
|
-
|
|
260
|
-
let
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if ready {
|
|
269
|
-
return (true, "Use codex-lb: active after restart · provider, auth, and routing ready.")
|
|
231
|
+
private func capabilityState(_ key: String, payload: [String: Any], allowVerified: Bool) -> String? {
|
|
232
|
+
let state: String?
|
|
233
|
+
if let evidence = payload[key] as? [String: Any] {
|
|
234
|
+
state = normalizedCapabilityState(evidence["state"] as? String, source: evidence["source"] as? String)
|
|
235
|
+
} else if let rawState = payload[key] as? String {
|
|
236
|
+
state = normalizedCapabilityState(rawState, source: nil)
|
|
237
|
+
} else {
|
|
238
|
+
var listedState: String?
|
|
239
|
+
for candidate in ["verified", "available_unverified", "unsupported", "skipped"] { if let keys = payload[candidate] as? [String], keys.contains(key) { listedState = candidate } }
|
|
240
|
+
if let blocked = payload["blocked"] as? [String: Any], blocked[key] != nil { listedState = "blocked" }
|
|
241
|
+
if key == "oauth_identity", let preserved = payload["oauth_preserved"] as? Bool { listedState = preserved ? "available_unverified" : "blocked" }
|
|
242
|
+
state = listedState
|
|
270
243
|
}
|
|
271
|
-
|
|
272
|
-
let next = nextAction(status: activationStatus, codexLb: codexLb, readiness: readiness)
|
|
273
|
-
return (false, "Use codex-lb: \(activationStatus) · Reason: \(reason) · Next: \(next).")
|
|
244
|
+
return state == "verified" && !allowVerified ? "available_unverified" : state
|
|
274
245
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
return (true, "Chat / Pro restored with ChatGPT OAuth · Codex App restarted · codex-lb credentials kept.")
|
|
289
|
-
}
|
|
290
|
-
if status == "no_backup" {
|
|
291
|
-
return (false, "Restore Chat / Pro failed (no_backup) · Run codex login, then retry. \(noSwitch)")
|
|
292
|
-
}
|
|
293
|
-
if status == "auth_in_use" {
|
|
294
|
-
return (false, "Restore Chat / Pro failed (auth_in_use) · Open Diagnostics; auth was not replaced. \(noSwitch)")
|
|
295
|
-
}
|
|
296
|
-
if !restartPerformed {
|
|
297
|
-
return (false, "Restore Chat / Pro failed (restart_not_performed) · Retry, then reopen Codex App. \(noSwitch)")
|
|
246
|
+
private func normalizedCapabilityState(_ state: String?, source: String?) -> String? {
|
|
247
|
+
guard let state = state?.lowercased(), capabilityStates.contains(state) else { return nil }
|
|
248
|
+
if state == "verified", source == "config" || source == "manifest" { return "available_unverified" }
|
|
249
|
+
return state
|
|
250
|
+
}
|
|
251
|
+
private func capabilityStateRank(_ state: String) -> Int {
|
|
252
|
+
switch state {
|
|
253
|
+
case "blocked": return 5
|
|
254
|
+
case "unsupported": return 4
|
|
255
|
+
case "skipped": return 3
|
|
256
|
+
case "available_unverified": return 2
|
|
257
|
+
case "verified": return 1
|
|
258
|
+
default: return 0
|
|
298
259
|
}
|
|
299
|
-
return (false, "Restore Chat / Pro failed (\(status)) · Retry or open Diagnostics. \(noSwitch)")
|
|
300
260
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
let global = json["global"] as? [String: Any], let on = global["on"] as? Bool else {
|
|
308
|
-
self.fastStatus.stringValue = "Fast Mode: unavailable — no state was assumed."
|
|
309
|
-
return
|
|
310
|
-
}
|
|
311
|
-
let tier = global["service_tier"] as? String ?? (on ? "fast" : "default")
|
|
312
|
-
self.fastStatus.stringValue = "Fast Mode: \(on ? "On" : "Off") (service_tier=\(tier))."
|
|
261
|
+
private func capabilityColor(_ state: String) -> NSColor {
|
|
262
|
+
switch state {
|
|
263
|
+
case "verified": return .systemGreen
|
|
264
|
+
case "blocked": return .systemRed
|
|
265
|
+
case "unsupported", "skipped": return .secondaryLabelColor
|
|
266
|
+
default: return .systemOrange
|
|
313
267
|
}
|
|
314
268
|
}
|
|
315
|
-
|
|
316
269
|
func json(_ output: String) -> [String: Any]? {
|
|
317
270
|
guard let data = output.data(using: .utf8) else { return nil }
|
|
318
271
|
return try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
319
272
|
}
|
|
320
|
-
|
|
321
273
|
@objc private func setDomainAndKey() {
|
|
322
274
|
guard let window = view.window else { return }
|
|
323
|
-
AlertFactory.textSheet(
|
|
324
|
-
window: window,
|
|
325
|
-
title: "Codex LB Domain",
|
|
326
|
-
message: "Enter a hostname or full base URL. https:// is optional — SKS adds https:// and /backend-api/codex when missing.\nExamples: lb.example.com or https://lb.example.com",
|
|
327
|
-
secure: false,
|
|
328
|
-
placeholder: "https://lb.example.com"
|
|
329
|
-
) { [weak self] host in
|
|
275
|
+
AlertFactory.textSheet(window: window, title: "Codex LB Domain", message: "Enter a hostname or full base URL. https:// is optional — SKS adds https:// and /backend-api/codex when missing.\nExamples: lb.example.com or https://lb.example.com", secure: false, placeholder: "https://lb.example.com") { [weak self] host in
|
|
330
276
|
guard let self = self, let host = host else { return }
|
|
331
|
-
self.promptForKey(window: window, args: ["codex-lb", "setup", "--host", host, "--api-key-stdin", "--yes", "--
|
|
277
|
+
self.promptForKey(window: window, args: ["codex-lb", "setup", "--host", host, "--api-key-stdin", "--yes", "--write-env-file", "--keychain", "--json"], kind: "codex-lb-center-setup", title: "Save Codex LB credentials")
|
|
332
278
|
}
|
|
333
279
|
}
|
|
334
|
-
|
|
335
|
-
@objc private func replaceKey() {
|
|
336
|
-
guard let window = view.window else { return }
|
|
337
|
-
promptForKey(window: window, args: ["codex-lb", "set-key", "--api-key-stdin", "--preserve-auth", "--restart-app", "--json"], kind: "codex-lb-set-key", title: "Replace Codex LB API key")
|
|
338
|
-
}
|
|
339
|
-
|
|
340
280
|
private func promptForKey(window: NSWindow, args: [String], kind: String, title: String) {
|
|
341
|
-
promptForSecretKey(
|
|
342
|
-
window: window,
|
|
343
|
-
sheetTitle: "Codex LB API Key",
|
|
344
|
-
sheetMessage: "Paste your Codex LB API key (usually starts with sk-clb-). It is shown here so you can verify the paste, then sent through stdin and never logged.",
|
|
345
|
-
placeholder: "sk-clb-…",
|
|
346
|
-
args: args,
|
|
347
|
-
kind: kind,
|
|
348
|
-
title: title,
|
|
349
|
-
statusLabel: providerStatus,
|
|
350
|
-
successSummary: "Codex LB API key saved",
|
|
351
|
-
failSummary: "Codex LB API key save failed"
|
|
352
|
-
)
|
|
281
|
+
promptForSecretKey(window: window, sheetTitle: "Codex LB API Key", sheetMessage: "Paste your Codex LB API key (usually starts with sk-clb-). The field stays masked; the key is sent through stdin and stored as the official SKS Center credential Desktop uses automatically.", placeholder: "sk-clb-…", args: args, kind: kind, title: title, statusLabel: cliProviderStatus, successSummary: "Codex LB credentials saved for Center/Desktop", failSummary: "Codex LB credential save failed")
|
|
353
282
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
window: NSWindow,
|
|
357
|
-
sheetTitle: String,
|
|
358
|
-
sheetMessage: String,
|
|
359
|
-
placeholder: String,
|
|
360
|
-
args: [String],
|
|
361
|
-
kind: String,
|
|
362
|
-
title: String,
|
|
363
|
-
statusLabel: NSTextField,
|
|
364
|
-
successSummary: String,
|
|
365
|
-
failSummary: String
|
|
366
|
-
) {
|
|
367
|
-
AlertFactory.textSheet(window: window, title: sheetTitle, message: sheetMessage, secure: false, placeholder: placeholder) { [weak self] key in
|
|
283
|
+
func promptForSecretKey(window: NSWindow, sheetTitle: String, sheetMessage: String, placeholder: String, args: [String], kind: String, title: String, statusLabel: NSTextField, successSummary: String, failSummary: String) {
|
|
284
|
+
AlertFactory.textSheet(window: window, title: sheetTitle, message: sheetMessage, secure: true, placeholder: placeholder) { [weak self] key in
|
|
368
285
|
guard let self = self, let key = key else { return }
|
|
369
|
-
guard !self.busy else {
|
|
370
|
-
statusLabel.stringValue = "Another provider action is already running."
|
|
371
|
-
return
|
|
372
|
-
}
|
|
286
|
+
guard !self.busy else { statusLabel.stringValue = "Another provider action is already running."; return }
|
|
373
287
|
guard let snapshot = self.operations.begin(kind: kind, mutationGroup: "codex-config", summary: title) else {
|
|
374
288
|
statusLabel.stringValue = "Another guarded mutation is already running. Wait or open Diagnostics."
|
|
375
289
|
return
|
|
@@ -380,118 +294,151 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
|
|
|
380
294
|
self.processClient.run(args, stdin: key + "\n", timeout: NativeView.mutationTimeout) { [weak self] result in
|
|
381
295
|
guard let self = self else { return }
|
|
382
296
|
self.setBusy(false)
|
|
383
|
-
let
|
|
297
|
+
let parsed = self.json(result.output)
|
|
298
|
+
let ok = result.code == 0 && parsed?["ok"] as? Bool == true
|
|
384
299
|
_ = self.operations.update(snapshot, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: ok ? successSummary : failSummary)
|
|
385
|
-
statusLabel.stringValue = ok
|
|
300
|
+
statusLabel.stringValue = ok
|
|
301
|
+
? "\(successSummary). Next: test the saved configuration."
|
|
302
|
+
: "\(failSummary) · \(self.structuredPublicDetail(parsed, fallback: result.output))"
|
|
386
303
|
self.refresh()
|
|
387
304
|
}
|
|
388
305
|
}
|
|
389
306
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
307
|
+
@objc private func enableDesktopFull() {
|
|
308
|
+
performDesktopRouting(["codex-lb", "use-desktop-full", "--restart-app", "--json"], title: "Use Codex LB", kind: "codex-lb-use-desktop-full", expectedMode: "desktop-native-bridge")
|
|
309
|
+
}
|
|
310
|
+
@objc private func disableDesktopRouting() {
|
|
311
|
+
performDesktopRouting(["codex-lb", "disable", "--restart-app", "--json"], title: "Use ChatGPT OAuth Only", kind: "codex-lb-disable-desktop", expectedMode: "disabled")
|
|
312
|
+
}
|
|
313
|
+
private func performDesktopRouting(_ args: [String], title: String, kind: String, expectedMode: String) {
|
|
314
|
+
guard !busy else { providerStatus.stringValue = "Another provider action is already running."; return }
|
|
315
|
+
guard let snapshot = operations.begin(kind: kind, mutationGroup: "codex-config", summary: title) else {
|
|
397
316
|
providerStatus.stringValue = "Another guarded mutation is already running. Wait or open Diagnostics."
|
|
398
317
|
return
|
|
399
318
|
}
|
|
400
319
|
setBusy(true)
|
|
401
|
-
providerStatus.stringValue = "
|
|
402
|
-
_ = operations.update(snapshot, state: .running, stage: "
|
|
403
|
-
processClient.run(
|
|
320
|
+
providerStatus.stringValue = "\(title)…"
|
|
321
|
+
_ = operations.update(snapshot, state: .running, stage: "routing", progress: nil, summary: title)
|
|
322
|
+
processClient.run(args, timeout: NativeView.mutationTimeout) { [weak self] result in
|
|
404
323
|
guard let self = self else { return }
|
|
405
324
|
self.setBusy(false)
|
|
406
325
|
let parsed = self.json(result.output)
|
|
407
|
-
let
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
self.
|
|
326
|
+
let routing = parsed.map(ProviderRoutingTruth.snapshot(from:))
|
|
327
|
+
let mode = routing?.mode
|
|
328
|
+
let oauthPreserved = routing.flatMap { $0.oauthPreservedFlag }
|
|
329
|
+
let ok = result.code == 0 && parsed?["ok"] as? Bool == true && mode == expectedMode && oauthPreserved != false
|
|
330
|
+
_ = self.operations.update(snapshot, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: ok ? "\(title) completed" : "\(title) needs action")
|
|
331
|
+
guard ok else {
|
|
332
|
+
self.providerStatus.stringValue = "\(title) was not confirmed · \(self.structuredPublicDetail(parsed, fallback: result.output)) No routing or OAuth change was assumed."
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
self.desktopFullRoutingNow = expectedMode == "desktop-native-bridge"
|
|
336
|
+
self.codexLbSelectedNow = false
|
|
337
|
+
self.chatgptOauthPresentNow = routing?.chatgptOauthPresent ?? self.chatgptOauthPresentNow
|
|
338
|
+
self.renderActiveProviderSummary()
|
|
339
|
+
self.providerStatus.stringValue = self.desktopFullRoutingNow
|
|
340
|
+
? "Codex LB mode enabled · OAuth preserved · capability verification still required."
|
|
341
|
+
: "ChatGPT OAuth mode active · Codex LB Desktop routing disabled."
|
|
342
|
+
self.refresh()
|
|
417
343
|
}
|
|
418
344
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
guard
|
|
422
|
-
|
|
345
|
+
@objc private func verifyDesktopCapabilities() {
|
|
346
|
+
guard !busy else { capabilityStatus.stringValue = "Another provider action is already running."; return }
|
|
347
|
+
guard let snapshot = operations.begin(kind: "codex-lb-capabilities", mutationGroup: nil, summary: "Verify Desktop capabilities") else {
|
|
348
|
+
capabilityStatus.stringValue = "Another guarded operation is already running. Wait or open Diagnostics."
|
|
423
349
|
return
|
|
424
350
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
self.
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
summary: outcome.ok ? "Chat / Pro restored after restart" : "Chat / Pro restore needs action"
|
|
445
|
-
)
|
|
446
|
-
self.providerStatus.stringValue = outcome.message
|
|
447
|
-
self.refreshFastStatus()
|
|
351
|
+
setBusy(true)
|
|
352
|
+
capabilityStatus.stringValue = "Verifying transport capabilities…"
|
|
353
|
+
_ = operations.update(snapshot, state: .running, stage: "verifying", progress: nil, summary: "Verify Desktop capabilities")
|
|
354
|
+
processClient.run(["codex-lb", "capabilities", "--level", "transport", "--json"], timeout: NativeView.mutationTimeout) { [weak self] result in
|
|
355
|
+
guard let self = self else { return }
|
|
356
|
+
self.setBusy(false)
|
|
357
|
+
let parsed = self.json(result.output)
|
|
358
|
+
let overall = parsed.flatMap(self.renderCapabilityMatrix)
|
|
359
|
+
let verified = result.code == 0
|
|
360
|
+
&& parsed?["ok"] as? Bool != false
|
|
361
|
+
&& overall == "verified"
|
|
362
|
+
&& parsed.map(CapabilityVerificationTruth.deepEvidenceTrusted) == true
|
|
363
|
+
_ = self.operations.update(snapshot, state: verified ? .succeeded : .failed, stage: "complete", progress: 1, summary: verified ? "Desktop capabilities verified" : "Desktop capability verification blocked")
|
|
364
|
+
if overall == nil {
|
|
365
|
+
self.capabilityStatus.stringValue = "Capability verification unavailable · structured evidence was not returned. No capability was assumed."
|
|
366
|
+
} else if !verified {
|
|
367
|
+
let blockers = parsed.map(CapabilityVerificationTruth.blockers) ?? []
|
|
368
|
+
let detail = blockers.isEmpty ? "overall=\(overall ?? "available_unverified")" : blockers.prefix(3).joined(separator: ", ")
|
|
369
|
+
self.capabilityStatus.stringValue = "Capability verification blocked · \(detail). No readiness was assumed."
|
|
448
370
|
}
|
|
449
371
|
}
|
|
450
372
|
}
|
|
451
|
-
@objc private func
|
|
452
|
-
|
|
453
|
-
|
|
373
|
+
@objc private func useCliProvider() {
|
|
374
|
+
performCliCommand(["codex-lb", "use-cli", "--json"], title: "Configure saved CLI provider", kind: "codex-lb-use-cli")
|
|
375
|
+
}
|
|
376
|
+
private func performCliCommand(_ args: [String], title: String, kind: String) {
|
|
377
|
+
guard !busy else { cliProviderStatus.stringValue = "Another provider action is already running."; return }
|
|
378
|
+
guard let snapshot = operations.begin(kind: kind, mutationGroup: "codex-config", summary: title) else {
|
|
379
|
+
cliProviderStatus.stringValue = "Another guarded mutation is already running. Wait or open Diagnostics."
|
|
454
380
|
return
|
|
455
381
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
382
|
+
setBusy(true)
|
|
383
|
+
cliProviderStatus.stringValue = "\(title)…"
|
|
384
|
+
_ = operations.update(snapshot, state: .running, stage: "configuring", progress: nil, summary: title)
|
|
385
|
+
processClient.run(args, timeout: NativeView.mutationTimeout) { [weak self] result in
|
|
386
|
+
guard let self = self else { return }
|
|
387
|
+
self.setBusy(false)
|
|
388
|
+
let parsed = self.json(result.output)
|
|
389
|
+
let routing = parsed.map(ProviderRoutingTruth.snapshot(from:))
|
|
390
|
+
let mode = routing?.mode
|
|
391
|
+
let oauthPreserved = routing.flatMap { $0.oauthPreservedFlag }
|
|
392
|
+
let authMutated = routing.flatMap { $0.authMutated }
|
|
393
|
+
let ok = result.code == 0 && parsed?["ok"] as? Bool == true && mode == "cli-provider" && oauthPreserved != false && authMutated != true
|
|
394
|
+
_ = self.operations.update(snapshot, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: ok ? "CLI provider configured" : "CLI provider configuration needs action")
|
|
395
|
+
self.cliProviderStatus.stringValue = ok
|
|
396
|
+
? "CLI provider configured · Desktop OAuth and global provider selection unchanged · Test still required."
|
|
397
|
+
: "CLI provider was not confirmed · \(self.structuredPublicDetail(parsed, fallback: result.output))"
|
|
398
|
+
if ok { self.refresh() }
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
@objc private func testConnection() {
|
|
402
|
+
guard !busy else { cliProviderStatus.stringValue = "Another provider action is already running."; return }
|
|
403
|
+
guard let snapshot = operations.begin(kind: "codex-lb-health", mutationGroup: nil, summary: "Test CLI provider") else {
|
|
404
|
+
cliProviderStatus.stringValue = "Another guarded operation is already running. Wait or open Diagnostics."
|
|
405
|
+
return
|
|
406
|
+
}
|
|
407
|
+
setBusy(true)
|
|
408
|
+
cliProviderStatus.stringValue = "Testing CLI provider transport…"
|
|
409
|
+
_ = operations.update(snapshot, state: .running, stage: "testing", progress: nil, summary: "Test CLI provider")
|
|
410
|
+
processClient.run(["codex-lb", "health", "--json"], timeout: NativeView.mutationTimeout) { [weak self] result in
|
|
411
|
+
guard let self = self else { return }
|
|
412
|
+
self.setBusy(false)
|
|
413
|
+
let parsed = self.json(result.output)
|
|
414
|
+
let ok = result.code == 0 && parsed?["ok"] as? Bool == true
|
|
415
|
+
let status = parsed?["status"] as? String ?? (ok ? "verified" : "failed")
|
|
416
|
+
_ = self.operations.update(snapshot, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: ok ? "CLI provider transport verified" : "CLI provider test needs action")
|
|
417
|
+
self.cliProviderStatus.stringValue = ok
|
|
418
|
+
? "CLI provider transport verified (\(status)). Desktop routing and OAuth were not changed."
|
|
419
|
+
: "CLI provider test failed (\(status)) · \(self.structuredPublicDetail(parsed, fallback: result.output))"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
@objc private func copyCliCommand() {
|
|
423
|
+
NSPasteboard.general.clearContents()
|
|
424
|
+
let copied = NSPasteboard.general.setString(cliLaunchCommand, forType: .string)
|
|
425
|
+
cliProviderStatus.stringValue = copied
|
|
426
|
+
? "Copied CLI command: \(cliLaunchCommand)"
|
|
427
|
+
: "Could not copy the CLI command. Select it above and copy manually."
|
|
428
|
+
}
|
|
429
|
+
private func refreshFastStatus() {
|
|
430
|
+
if !busy { fastStatus.stringValue = "Codex Fast: checking the official service-tier setting…" }
|
|
431
|
+
processClient.run(["fast-mode", "status", "--json"], timeout: NativeView.statusTimeout) { [weak self] result in
|
|
432
|
+
guard let self = self else { return }
|
|
433
|
+
guard result.code == 0, let json = self.json(result.output),
|
|
434
|
+
let global = json["global"] as? [String: Any], let on = global["on"] as? Bool else {
|
|
435
|
+
self.fastStatus.stringValue = "Codex Fast: unavailable — no state was assumed."
|
|
461
436
|
return
|
|
462
437
|
}
|
|
463
|
-
|
|
464
|
-
self.
|
|
465
|
-
_ = self.operations.update(snapshot, state: .running, stage: "activating", progress: nil, summary: "Use codex-lb")
|
|
466
|
-
self.processClient.run(["codex-lb", "use-codex-lb", "--restart-app", "--json"], timeout: NativeView.mutationTimeout) { [weak self] activationResult in
|
|
467
|
-
guard let self = self else { return }
|
|
468
|
-
let activationJson = self.json(activationResult.output)
|
|
469
|
-
_ = self.operations.update(snapshot, state: .running, stage: "verifying", progress: nil, summary: "Verify codex-lb after restart")
|
|
470
|
-
self.providerStatus.stringValue = "Verifying codex-lb readiness after restart…"
|
|
471
|
-
self.processClient.run(["codex-lb", "status", "--json"], timeout: NativeView.statusTimeout) { [weak self] statusResult in
|
|
472
|
-
guard let self = self else { return }
|
|
473
|
-
self.setBusy(false)
|
|
474
|
-
let statusJson = self.json(statusResult.output)
|
|
475
|
-
let outcome = self.describeActivationResult(
|
|
476
|
-
activationJson,
|
|
477
|
-
status: statusJson,
|
|
478
|
-
activationCode: activationResult.code,
|
|
479
|
-
statusCode: statusResult.code
|
|
480
|
-
)
|
|
481
|
-
_ = self.operations.update(
|
|
482
|
-
snapshot,
|
|
483
|
-
state: outcome.ok ? .succeeded : .failed,
|
|
484
|
-
stage: "complete",
|
|
485
|
-
progress: 1,
|
|
486
|
-
summary: outcome.ok ? "codex-lb active after restart" : "codex-lb activation needs action"
|
|
487
|
-
)
|
|
488
|
-
self.providerStatus.stringValue = outcome.message
|
|
489
|
-
self.refreshOpenRouterStatus()
|
|
490
|
-
}
|
|
491
|
-
}
|
|
438
|
+
let tier = global["service_tier"] as? String ?? (on ? "fast" : "default")
|
|
439
|
+
self.fastStatus.stringValue = "Codex Fast: \(on ? "On" : "Off") · official service_tier=\(tier) · model and reasoning remain separate."
|
|
492
440
|
}
|
|
493
441
|
}
|
|
494
|
-
|
|
495
|
-
@objc private func
|
|
496
|
-
@objc private func fastOff() { run(["fast-mode", "off", "--json"], title: "Fast Mode Off", kind: "fast-mode-off", group: "codex-config", timeout: NativeView.statusTimeout) { [weak self] in self?.refreshFastStatus() } }
|
|
442
|
+
@objc private func fastOn() { run(["fast-mode", "on", "--json"], title: "Codex Fast On", kind: "fast-mode-on", group: "codex-config", timeout: NativeView.statusTimeout) { [weak self] in self?.refreshFastStatus() } }
|
|
443
|
+
@objc private func fastOff() { run(["fast-mode", "off", "--json"], title: "Codex Fast Off", kind: "fast-mode-off", group: "codex-config", timeout: NativeView.statusTimeout) { [weak self] in self?.refreshFastStatus() } }
|
|
497
444
|
}
|