stagent 0.9.6 → 0.11.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 +20 -44
- package/dist/cli.js +66 -18
- package/docs/.coverage-gaps.json +144 -56
- package/docs/.last-generated +1 -1
- package/docs/features/agent-intelligence.md +12 -2
- package/docs/features/chat.md +40 -5
- package/docs/features/cost-usage.md +1 -1
- package/docs/features/documents.md +5 -2
- package/docs/features/inbox-notifications.md +10 -2
- package/docs/features/keyboard-navigation.md +12 -3
- package/docs/features/provider-runtimes.md +20 -2
- package/docs/features/schedules.md +32 -4
- package/docs/features/settings.md +28 -5
- package/docs/features/shared-components.md +7 -3
- package/docs/features/tables.md +11 -2
- package/docs/features/tool-permissions.md +6 -2
- package/docs/features/workflows.md +14 -4
- package/docs/index.md +1 -1
- package/docs/journeys/developer.md +39 -2
- package/docs/journeys/personal-use.md +32 -8
- package/docs/journeys/power-user.md +45 -14
- package/docs/journeys/work-use.md +17 -8
- package/docs/manifest.json +15 -15
- package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
- package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
- package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
- package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
- package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
- package/docs/superpowers/plans/2026-04-14-chat-command-namespace-refactor.md +1390 -0
- package/docs/superpowers/plans/2026-04-14-chat-environment-integration.md +1561 -0
- package/docs/superpowers/plans/2026-04-14-chat-polish-bundle-v1.md +1219 -0
- package/docs/superpowers/plans/2026-04-14-chat-session-persistence-provider-closeout.md +399 -0
- package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
- package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
- package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
- package/next.config.mjs +1 -0
- package/package.json +3 -2
- package/src/__tests__/instrumentation-smoke.test.ts +15 -0
- package/src/app/analytics/page.tsx +1 -21
- package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
- package/src/app/api/chat/conversations/[id]/skills/__tests__/activate.test.ts +141 -0
- package/src/app/api/chat/conversations/[id]/skills/activate/route.ts +74 -0
- package/src/app/api/chat/conversations/[id]/skills/deactivate/route.ts +33 -0
- package/src/app/api/chat/export/route.ts +52 -0
- package/src/app/api/chat/files/search/route.ts +50 -0
- package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
- package/src/app/api/environment/rescan-if-stale/__tests__/route.test.ts +45 -0
- package/src/app/api/environment/rescan-if-stale/route.ts +23 -0
- package/src/app/api/environment/skills/route.ts +13 -0
- package/src/app/api/instance/config/route.ts +41 -0
- package/src/app/api/instance/init/route.ts +34 -0
- package/src/app/api/instance/upgrade/check/route.ts +26 -0
- package/src/app/api/instance/upgrade/route.ts +96 -0
- package/src/app/api/instance/upgrade/status/route.ts +35 -0
- package/src/app/api/memory/route.ts +0 -11
- package/src/app/api/notifications/route.ts +4 -2
- package/src/app/api/projects/[id]/route.ts +5 -155
- package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
- package/src/app/api/schedules/[id]/execute/route.ts +111 -0
- package/src/app/api/schedules/[id]/route.ts +9 -1
- package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
- package/src/app/api/schedules/route.ts +3 -12
- package/src/app/api/settings/chat/pins/route.ts +94 -0
- package/src/app/api/settings/chat/saved-searches/__tests__/route.test.ts +119 -0
- package/src/app/api/settings/chat/saved-searches/route.ts +79 -0
- package/src/app/api/settings/environment/route.ts +26 -0
- package/src/app/api/settings/openai/login/route.ts +22 -0
- package/src/app/api/settings/openai/logout/route.ts +7 -0
- package/src/app/api/settings/openai/route.ts +21 -1
- package/src/app/api/settings/providers/route.ts +35 -8
- package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
- package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
- package/src/app/api/tables/[id]/enrich/route.ts +147 -0
- package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
- package/src/app/api/tasks/[id]/execute/route.ts +52 -33
- package/src/app/api/tasks/[id]/respond/route.ts +31 -15
- package/src/app/api/tasks/[id]/resume/route.ts +24 -3
- package/src/app/api/workflows/[id]/resume/route.ts +59 -0
- package/src/app/api/workflows/[id]/status/route.ts +22 -8
- package/src/app/api/workspace/context/route.ts +2 -0
- package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
- package/src/app/chat/page.tsx +11 -0
- package/src/app/documents/page.tsx +4 -1
- package/src/app/inbox/page.tsx +12 -5
- package/src/app/layout.tsx +42 -21
- package/src/app/page.tsx +0 -2
- package/src/app/settings/page.tsx +8 -9
- package/src/components/chat/__tests__/capability-banner.test.tsx +38 -0
- package/src/components/chat/__tests__/chat-session-provider.test.tsx +573 -0
- package/src/components/chat/__tests__/skill-row.test.tsx +91 -0
- package/src/components/chat/capability-banner.tsx +68 -0
- package/src/components/chat/chat-command-popover.tsx +670 -49
- package/src/components/chat/chat-input.tsx +104 -10
- package/src/components/chat/chat-message.tsx +12 -3
- package/src/components/chat/chat-session-provider.tsx +790 -0
- package/src/components/chat/chat-shell.tsx +151 -401
- package/src/components/chat/command-tab-bar.tsx +68 -0
- package/src/components/chat/conversation-template-picker.tsx +421 -0
- package/src/components/chat/help-dialog.tsx +39 -0
- package/src/components/chat/skill-composition-conflict-dialog.tsx +96 -0
- package/src/components/chat/skill-row.tsx +147 -0
- package/src/components/documents/document-browser.tsx +37 -19
- package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
- package/src/components/instance/instance-section.tsx +382 -0
- package/src/components/instance/upgrade-badge.tsx +219 -0
- package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
- package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
- package/src/components/notifications/__tests__/permission-response-actions.test.tsx +70 -0
- package/src/components/notifications/batch-proposal-review.tsx +20 -5
- package/src/components/notifications/inbox-list.tsx +11 -2
- package/src/components/notifications/notification-item.tsx +56 -2
- package/src/components/notifications/pending-approval-host.tsx +56 -37
- package/src/components/notifications/permission-response-actions.tsx +155 -1
- package/src/components/schedules/schedule-create-sheet.tsx +19 -1
- package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
- package/src/components/schedules/schedule-form.tsx +31 -0
- package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
- package/src/components/settings/auth-method-selector.tsx +19 -4
- package/src/components/settings/auth-status-badge.tsx +28 -3
- package/src/components/settings/environment-section.tsx +102 -0
- package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
- package/src/components/settings/openai-runtime-section.tsx +7 -1
- package/src/components/settings/providers-runtimes-section.tsx +138 -19
- package/src/components/shared/__tests__/filter-hint.test.tsx +40 -0
- package/src/components/shared/__tests__/saved-searches-manager.test.tsx +147 -0
- package/src/components/shared/app-sidebar.tsx +4 -3
- package/src/components/shared/command-palette.tsx +266 -7
- package/src/components/shared/filter-hint.tsx +70 -0
- package/src/components/shared/filter-input.tsx +59 -0
- package/src/components/shared/saved-searches-manager.tsx +199 -0
- package/src/components/shared/theme-toggle.tsx +5 -24
- package/src/components/shared/workspace-indicator.tsx +61 -2
- package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
- package/src/components/tables/table-create-sheet.tsx +4 -0
- package/src/components/tables/table-enrichment-runs.tsx +103 -0
- package/src/components/tables/table-enrichment-sheet.tsx +538 -0
- package/src/components/tables/table-spreadsheet.tsx +29 -5
- package/src/components/tables/table-toolbar.tsx +10 -1
- package/src/components/tasks/kanban-board.tsx +1 -0
- package/src/components/tasks/kanban-column.tsx +53 -14
- package/src/components/tasks/task-bento-grid.tsx +31 -2
- package/src/components/tasks/task-card.tsx +29 -3
- package/src/components/tasks/task-chip-bar.tsx +54 -1
- package/src/components/tasks/task-result-renderer.tsx +1 -1
- package/src/components/workflows/delay-step-body.tsx +109 -0
- package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
- package/src/components/workflows/loop-status-view.tsx +1 -1
- package/src/components/workflows/shared/step-result.tsx +78 -0
- package/src/components/workflows/shared/workflow-header.tsx +141 -0
- package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
- package/src/components/workflows/swarm-dashboard.tsx +2 -15
- package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
- package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
- package/src/components/workflows/workflow-form-view.tsx +133 -16
- package/src/components/workflows/workflow-status-view.tsx +30 -740
- package/src/hooks/__tests__/use-chat-autocomplete-tabs.test.ts +47 -0
- package/src/hooks/__tests__/use-saved-searches.test.ts +70 -0
- package/src/hooks/use-active-skills.ts +110 -0
- package/src/hooks/use-chat-autocomplete.ts +120 -7
- package/src/hooks/use-enriched-skills.ts +19 -0
- package/src/hooks/use-pinned-entries.ts +104 -0
- package/src/hooks/use-recent-user-messages.ts +19 -0
- package/src/hooks/use-saved-searches.ts +142 -0
- package/src/instrumentation-node.ts +94 -0
- package/src/instrumentation.ts +4 -48
- package/src/lib/agents/__tests__/claude-agent-sdk-options.test.ts +56 -0
- package/src/lib/agents/__tests__/claude-agent.test.ts +212 -0
- package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
- package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
- package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
- package/src/lib/agents/__tests__/task-dispatch.test.ts +166 -0
- package/src/lib/agents/__tests__/tool-permissions.test.ts +60 -0
- package/src/lib/agents/claude-agent.ts +217 -21
- package/src/lib/agents/execution-manager.ts +0 -35
- package/src/lib/agents/handoff/bus.ts +2 -2
- package/src/lib/agents/learned-context.ts +0 -12
- package/src/lib/agents/learning-session.ts +18 -5
- package/src/lib/agents/profiles/__tests__/list-fused-profiles.test.ts +110 -0
- package/src/lib/agents/profiles/__tests__/registry.test.ts +53 -4
- package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +97 -0
- package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +36 -0
- package/src/lib/agents/profiles/list-fused-profiles.ts +104 -0
- package/src/lib/agents/profiles/registry.ts +18 -0
- package/src/lib/agents/profiles/types.ts +7 -1
- package/src/lib/agents/router.ts +3 -6
- package/src/lib/agents/runtime/__tests__/catalog.test.ts +130 -0
- package/src/lib/agents/runtime/__tests__/execution-target.test.ts +183 -0
- package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
- package/src/lib/agents/runtime/anthropic-direct.ts +8 -0
- package/src/lib/agents/runtime/catalog.ts +121 -0
- package/src/lib/agents/runtime/claude-sdk.ts +32 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
- package/src/lib/agents/runtime/execution-target.ts +456 -0
- package/src/lib/agents/runtime/index.ts +4 -0
- package/src/lib/agents/runtime/launch-failure.ts +101 -0
- package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
- package/src/lib/agents/runtime/openai-codex.ts +64 -60
- package/src/lib/agents/runtime/openai-direct.ts +8 -0
- package/src/lib/agents/runtime/types.ts +8 -0
- package/src/lib/agents/task-dispatch.ts +220 -0
- package/src/lib/agents/tool-permissions.ts +16 -1
- package/src/lib/book/chapter-mapping.ts +11 -0
- package/src/lib/book/content.ts +10 -0
- package/src/lib/chat/__tests__/active-skill-injection.test.ts +261 -0
- package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
- package/src/lib/chat/__tests__/clean-filter-input.test.ts +68 -0
- package/src/lib/chat/__tests__/command-tabs.test.ts +68 -0
- package/src/lib/chat/__tests__/context-builder-files.test.ts +112 -0
- package/src/lib/chat/__tests__/dismissals.test.ts +65 -0
- package/src/lib/chat/__tests__/engine-sdk-options.test.ts +117 -0
- package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
- package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
- package/src/lib/chat/__tests__/skill-conflict.test.ts +35 -0
- package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
- package/src/lib/chat/__tests__/types.test.ts +28 -0
- package/src/lib/chat/active-skills.ts +31 -0
- package/src/lib/chat/active-streams.ts +27 -0
- package/src/lib/chat/clean-filter-input.ts +30 -0
- package/src/lib/chat/codex-engine.ts +46 -24
- package/src/lib/chat/command-tabs.ts +61 -0
- package/src/lib/chat/context-builder.ts +146 -4
- package/src/lib/chat/dismissals.ts +73 -0
- package/src/lib/chat/engine.ts +159 -18
- package/src/lib/chat/files/__tests__/search.test.ts +135 -0
- package/src/lib/chat/files/expand-mention.ts +76 -0
- package/src/lib/chat/files/search.ts +99 -0
- package/src/lib/chat/reconcile.ts +117 -0
- package/src/lib/chat/skill-composition.ts +210 -0
- package/src/lib/chat/skill-conflict.ts +105 -0
- package/src/lib/chat/stagent-tools.ts +7 -19
- package/src/lib/chat/stream-telemetry.ts +137 -0
- package/src/lib/chat/suggested-prompts.ts +28 -1
- package/src/lib/chat/system-prompt.ts +48 -1
- package/src/lib/chat/tool-catalog.ts +35 -4
- package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
- package/src/lib/chat/tools/__tests__/profile-tools.test.ts +51 -0
- package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
- package/src/lib/chat/tools/__tests__/settings-tools.test.ts +294 -0
- package/src/lib/chat/tools/__tests__/skill-tools.test.ts +474 -0
- package/src/lib/chat/tools/__tests__/task-tools.test.ts +399 -0
- package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +351 -0
- package/src/lib/chat/tools/blueprint-tools.ts +190 -0
- package/src/lib/chat/tools/document-tools.ts +29 -13
- package/src/lib/chat/tools/helpers.ts +41 -0
- package/src/lib/chat/tools/notification-tools.ts +9 -5
- package/src/lib/chat/tools/profile-tools.ts +120 -23
- package/src/lib/chat/tools/project-tools.ts +33 -0
- package/src/lib/chat/tools/schedule-tools.ts +44 -11
- package/src/lib/chat/tools/skill-tools.ts +183 -0
- package/src/lib/chat/tools/table-tools.ts +71 -0
- package/src/lib/chat/tools/task-tools.ts +89 -21
- package/src/lib/chat/tools/workflow-tools.ts +275 -32
- package/src/lib/chat/types.ts +15 -0
- package/src/lib/constants/settings.ts +10 -18
- package/src/lib/data/__tests__/clear.test.ts +56 -2
- package/src/lib/data/clear.ts +17 -16
- package/src/lib/data/delete-project.ts +171 -0
- package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
- package/src/lib/db/bootstrap.ts +62 -16
- package/src/lib/db/index.ts +5 -0
- package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
- package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
- package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
- package/src/lib/db/migrations/0026_drop_license.sql +3 -0
- package/src/lib/db/migrations/meta/_journal.json +21 -0
- package/src/lib/db/schema.ts +94 -23
- package/src/lib/environment/__tests__/auto-promote.test.ts +132 -0
- package/src/lib/environment/__tests__/list-skills-enriched.test.ts +55 -0
- package/src/lib/environment/__tests__/skill-enrichment.test.ts +129 -0
- package/src/lib/environment/__tests__/skill-recommendations.test.ts +87 -0
- package/src/lib/environment/data.ts +9 -0
- package/src/lib/environment/list-skills.ts +176 -0
- package/src/lib/environment/parsers/__tests__/skill.test.ts +54 -0
- package/src/lib/environment/parsers/skill.ts +26 -5
- package/src/lib/environment/profile-generator.ts +54 -0
- package/src/lib/environment/skill-enrichment.ts +106 -0
- package/src/lib/environment/skill-recommendations.ts +66 -0
- package/src/lib/environment/workspace-context.ts +13 -1
- package/src/lib/filters/__tests__/parse.quoted.test.ts +40 -0
- package/src/lib/filters/__tests__/parse.test.ts +135 -0
- package/src/lib/filters/parse.ts +86 -0
- package/src/lib/import/dedup.ts +4 -54
- package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
- package/src/lib/instance/__tests__/detect.test.ts +115 -0
- package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
- package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
- package/src/lib/instance/__tests__/settings.test.ts +83 -0
- package/src/lib/instance/__tests__/upgrade-poller.test.ts +181 -0
- package/src/lib/instance/bootstrap.ts +270 -0
- package/src/lib/instance/detect.ts +49 -0
- package/src/lib/instance/fingerprint.ts +76 -0
- package/src/lib/instance/git-ops.ts +95 -0
- package/src/lib/instance/settings.ts +61 -0
- package/src/lib/instance/types.ts +77 -0
- package/src/lib/instance/upgrade-poller.ts +205 -0
- package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
- package/src/lib/notifications/visibility.ts +33 -0
- package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
- package/src/lib/schedules/__tests__/config.test.ts +62 -0
- package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
- package/src/lib/schedules/__tests__/integration.test.ts +82 -0
- package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
- package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
- package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
- package/src/lib/schedules/collision-check.ts +105 -0
- package/src/lib/schedules/config.ts +53 -0
- package/src/lib/schedules/scheduler.ts +236 -17
- package/src/lib/schedules/slot-claim.ts +105 -0
- package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
- package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
- package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
- package/src/lib/settings/openai-auth.ts +105 -10
- package/src/lib/settings/openai-login-manager.ts +260 -0
- package/src/lib/settings/runtime-setup.ts +14 -4
- package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
- package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
- package/src/lib/tables/enrichment-planner.ts +454 -0
- package/src/lib/tables/enrichment.ts +328 -0
- package/src/lib/tables/query-builder.ts +5 -2
- package/src/lib/tables/trigger-evaluator.ts +3 -2
- package/src/lib/theme.ts +71 -0
- package/src/lib/usage/ledger.ts +2 -18
- package/src/lib/util/__tests__/similarity.test.ts +106 -0
- package/src/lib/util/similarity.ts +77 -0
- package/src/lib/utils/format-timestamp.ts +24 -0
- package/src/lib/utils/stagent-paths.ts +12 -0
- package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
- package/src/lib/validators/__tests__/settings.test.ts +10 -0
- package/src/lib/validators/blueprint.ts +70 -9
- package/src/lib/validators/profile.ts +2 -2
- package/src/lib/validators/settings.ts +3 -1
- package/src/lib/workflows/__tests__/delay.test.ts +196 -0
- package/src/lib/workflows/__tests__/engine.test.ts +8 -0
- package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
- package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
- package/src/lib/workflows/blueprints/__tests__/render-prompt.test.ts +124 -0
- package/src/lib/workflows/blueprints/instantiator.ts +22 -1
- package/src/lib/workflows/blueprints/render-prompt.ts +71 -0
- package/src/lib/workflows/blueprints/types.ts +16 -2
- package/src/lib/workflows/delay.ts +106 -0
- package/src/lib/workflows/engine.ts +212 -7
- package/src/lib/workflows/loop-executor.ts +349 -24
- package/src/lib/workflows/post-action.ts +91 -0
- package/src/lib/workflows/types.ts +166 -1
- package/src/test/setup.ts +10 -0
- package/src/app/api/license/checkout/route.ts +0 -28
- package/src/app/api/license/portal/route.ts +0 -26
- package/src/app/api/license/route.ts +0 -89
- package/src/app/api/license/usage/route.ts +0 -63
- package/src/app/api/marketplace/browse/route.ts +0 -15
- package/src/app/api/marketplace/import/route.ts +0 -28
- package/src/app/api/marketplace/publish/route.ts +0 -40
- package/src/app/api/onboarding/email/route.ts +0 -53
- package/src/app/api/settings/telemetry/route.ts +0 -14
- package/src/app/api/sync/export/route.ts +0 -54
- package/src/app/api/sync/restore/route.ts +0 -37
- package/src/app/api/sync/sessions/route.ts +0 -24
- package/src/app/auth/callback/route.ts +0 -73
- package/src/app/marketplace/page.tsx +0 -19
- package/src/components/analytics/analytics-gate-card.tsx +0 -101
- package/src/components/marketplace/blueprint-card.tsx +0 -61
- package/src/components/marketplace/marketplace-browser.tsx +0 -131
- package/src/components/onboarding/email-capture-card.tsx +0 -104
- package/src/components/settings/activation-form.tsx +0 -95
- package/src/components/settings/cloud-account-section.tsx +0 -147
- package/src/components/settings/cloud-sync-section.tsx +0 -155
- package/src/components/settings/subscription-section.tsx +0 -410
- package/src/components/settings/telemetry-section.tsx +0 -80
- package/src/components/shared/premium-gate-overlay.tsx +0 -50
- package/src/components/shared/schedule-gate-dialog.tsx +0 -64
- package/src/components/shared/upgrade-banner.tsx +0 -112
- package/src/hooks/use-supabase-auth.ts +0 -79
- package/src/lib/billing/email.ts +0 -54
- package/src/lib/billing/products.ts +0 -80
- package/src/lib/billing/stripe.ts +0 -101
- package/src/lib/cloud/supabase-browser.ts +0 -32
- package/src/lib/cloud/supabase-client.ts +0 -56
- package/src/lib/license/__tests__/features.test.ts +0 -56
- package/src/lib/license/__tests__/key-format.test.ts +0 -88
- package/src/lib/license/__tests__/manager.test.ts +0 -64
- package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
- package/src/lib/license/cloud-validation.ts +0 -60
- package/src/lib/license/features.ts +0 -44
- package/src/lib/license/key-format.ts +0 -101
- package/src/lib/license/limit-check.ts +0 -111
- package/src/lib/license/limit-queries.ts +0 -51
- package/src/lib/license/manager.ts +0 -345
- package/src/lib/license/notifications.ts +0 -59
- package/src/lib/license/tier-limits.ts +0 -71
- package/src/lib/marketplace/marketplace-client.ts +0 -107
- package/src/lib/sync/cloud-sync.ts +0 -235
- package/src/lib/telemetry/conversion-events.ts +0 -71
- package/src/lib/telemetry/queue.ts +0 -122
- package/src/lib/validators/license.ts +0 -33
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from "react";
|
|
4
|
+
import { Sparkles, Wand2 } from "lucide-react";
|
|
5
|
+
import { toast } from "sonner";
|
|
6
|
+
import {
|
|
7
|
+
Card,
|
|
8
|
+
CardContent,
|
|
9
|
+
CardDescription,
|
|
10
|
+
CardHeader,
|
|
11
|
+
CardTitle,
|
|
12
|
+
} from "@/components/ui/card";
|
|
13
|
+
import { Switch } from "@/components/ui/switch";
|
|
14
|
+
import { Label } from "@/components/ui/label";
|
|
15
|
+
import { FormSectionCard } from "@/components/shared/form-section-card";
|
|
16
|
+
|
|
17
|
+
interface EnvironmentState {
|
|
18
|
+
autoPromoteSkills: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DEFAULT_STATE: EnvironmentState = {
|
|
22
|
+
autoPromoteSkills: false,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function EnvironmentSection() {
|
|
26
|
+
const [state, setState] = useState<EnvironmentState>(DEFAULT_STATE);
|
|
27
|
+
const [saving, setSaving] = useState(false);
|
|
28
|
+
|
|
29
|
+
const fetchSettings = useCallback(async () => {
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch("/api/settings/environment");
|
|
32
|
+
if (res.ok) {
|
|
33
|
+
const data = await res.json();
|
|
34
|
+
setState(data);
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
// Use defaults
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
fetchSettings();
|
|
43
|
+
}, [fetchSettings]);
|
|
44
|
+
|
|
45
|
+
const handleToggle = async (value: boolean) => {
|
|
46
|
+
setState((prev) => ({ ...prev, autoPromoteSkills: value }));
|
|
47
|
+
setSaving(true);
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch("/api/settings/environment", {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { "Content-Type": "application/json" },
|
|
52
|
+
body: JSON.stringify({ autoPromoteSkills: value }),
|
|
53
|
+
});
|
|
54
|
+
if (res.ok) {
|
|
55
|
+
const data = await res.json();
|
|
56
|
+
setState(data);
|
|
57
|
+
toast.success(`Auto-promote ${value ? "enabled" : "disabled"}`);
|
|
58
|
+
} else {
|
|
59
|
+
throw new Error("Save failed");
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
toast.error("Failed to save setting");
|
|
63
|
+
setState((prev) => ({ ...prev, autoPromoteSkills: !value }));
|
|
64
|
+
} finally {
|
|
65
|
+
setSaving(false);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Card>
|
|
71
|
+
<CardHeader>
|
|
72
|
+
<CardTitle className="flex items-center gap-2">
|
|
73
|
+
<Sparkles className="h-5 w-5" />
|
|
74
|
+
Environment
|
|
75
|
+
</CardTitle>
|
|
76
|
+
<CardDescription>
|
|
77
|
+
How Stagent discovers and syncs skills from your environment into the
|
|
78
|
+
agent profile registry.
|
|
79
|
+
</CardDescription>
|
|
80
|
+
</CardHeader>
|
|
81
|
+
<CardContent className="space-y-4">
|
|
82
|
+
<FormSectionCard
|
|
83
|
+
icon={Wand2}
|
|
84
|
+
title="Auto-promote discovered skills"
|
|
85
|
+
hint="When enabled, every unlinked skill in ~/.claude/skills/ with a valid SKILL.md is automatically converted into an agent profile on the next environment scan. Leave off to review and promote skills manually from the Environment dashboard."
|
|
86
|
+
>
|
|
87
|
+
<div className="flex items-center justify-between">
|
|
88
|
+
<Label htmlFor="auto-promote-toggle" className="text-sm">
|
|
89
|
+
{state.autoPromoteSkills ? "Enabled" : "Disabled"}
|
|
90
|
+
</Label>
|
|
91
|
+
<Switch
|
|
92
|
+
id="auto-promote-toggle"
|
|
93
|
+
checked={state.autoPromoteSkills}
|
|
94
|
+
disabled={saving}
|
|
95
|
+
onCheckedChange={handleToggle}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
</FormSectionCard>
|
|
99
|
+
</CardContent>
|
|
100
|
+
</Card>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
CircleSlash,
|
|
6
|
+
ExternalLink,
|
|
7
|
+
Loader2,
|
|
8
|
+
LogOut,
|
|
9
|
+
RefreshCw,
|
|
10
|
+
ShieldCheck,
|
|
11
|
+
XCircle,
|
|
12
|
+
} from "lucide-react";
|
|
13
|
+
import { Button } from "@/components/ui/button";
|
|
14
|
+
import type {
|
|
15
|
+
OpenAIAccountInfo,
|
|
16
|
+
OpenAIRateLimitInfo,
|
|
17
|
+
} from "@/lib/settings/openai-auth";
|
|
18
|
+
import type { OpenAILoginState } from "@/lib/settings/openai-login-manager";
|
|
19
|
+
import type { RuntimeConnectionResult } from "@/lib/agents/runtime/types";
|
|
20
|
+
|
|
21
|
+
interface OpenAIChatGPTAuthControlProps {
|
|
22
|
+
connected: boolean;
|
|
23
|
+
account: OpenAIAccountInfo | null;
|
|
24
|
+
rateLimits: OpenAIRateLimitInfo | null;
|
|
25
|
+
initialLoginState: OpenAILoginState;
|
|
26
|
+
onChanged: () => Promise<void>;
|
|
27
|
+
onLoginStateChange?: (state: OpenAILoginState) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatResetAt(value: number | null | undefined) {
|
|
31
|
+
if (!value) return null;
|
|
32
|
+
return new Date(value * 1000).toLocaleString(undefined, {
|
|
33
|
+
dateStyle: "medium",
|
|
34
|
+
timeStyle: "short",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatPlanType(value: string | null | undefined) {
|
|
39
|
+
if (!value) return "Unknown";
|
|
40
|
+
|
|
41
|
+
switch (value.toLowerCase()) {
|
|
42
|
+
case "prolite":
|
|
43
|
+
case "pro":
|
|
44
|
+
return "Pro";
|
|
45
|
+
case "plus":
|
|
46
|
+
return "Plus";
|
|
47
|
+
case "business":
|
|
48
|
+
return "Business";
|
|
49
|
+
case "enterprise":
|
|
50
|
+
return "Enterprise";
|
|
51
|
+
case "edu":
|
|
52
|
+
case "education":
|
|
53
|
+
return "Education";
|
|
54
|
+
default:
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function OpenAIChatGPTAuthControl({
|
|
60
|
+
connected,
|
|
61
|
+
account,
|
|
62
|
+
rateLimits,
|
|
63
|
+
initialLoginState,
|
|
64
|
+
onChanged,
|
|
65
|
+
onLoginStateChange,
|
|
66
|
+
}: OpenAIChatGPTAuthControlProps) {
|
|
67
|
+
const [loginState, setLoginState] = useState<OpenAILoginState>(initialLoginState);
|
|
68
|
+
const [testResult, setTestResult] = useState<RuntimeConnectionResult | null>(null);
|
|
69
|
+
const [testing, setTesting] = useState(false);
|
|
70
|
+
const [signingOut, setSigningOut] = useState(false);
|
|
71
|
+
|
|
72
|
+
function updateLoginState(next: OpenAILoginState) {
|
|
73
|
+
setLoginState(next);
|
|
74
|
+
onLoginStateChange?.(next);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
updateLoginState(initialLoginState);
|
|
79
|
+
}, [initialLoginState]);
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (loginState.phase !== "pending") return;
|
|
83
|
+
|
|
84
|
+
const interval = window.setInterval(async () => {
|
|
85
|
+
const res = await fetch("/api/settings/openai/login");
|
|
86
|
+
if (!res.ok) return;
|
|
87
|
+
const next = (await res.json()) as OpenAILoginState;
|
|
88
|
+
updateLoginState(next);
|
|
89
|
+
if (next.phase !== "pending") {
|
|
90
|
+
window.clearInterval(interval);
|
|
91
|
+
await onChanged();
|
|
92
|
+
}
|
|
93
|
+
}, 1500);
|
|
94
|
+
|
|
95
|
+
return () => window.clearInterval(interval);
|
|
96
|
+
}, [loginState.phase, onChanged]);
|
|
97
|
+
|
|
98
|
+
async function handleStartLogin() {
|
|
99
|
+
setTestResult(null);
|
|
100
|
+
const res = await fetch("/api/settings/openai/login", { method: "POST" });
|
|
101
|
+
const next = (await res.json()) as OpenAILoginState;
|
|
102
|
+
updateLoginState(next);
|
|
103
|
+
|
|
104
|
+
if (next.authUrl) {
|
|
105
|
+
window.open(next.authUrl, "_blank", "noopener,noreferrer");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function handleCancelLogin() {
|
|
110
|
+
const res = await fetch("/api/settings/openai/login", { method: "DELETE" });
|
|
111
|
+
const next = (await res.json()) as OpenAILoginState;
|
|
112
|
+
updateLoginState(next);
|
|
113
|
+
await onChanged();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function handleLogout() {
|
|
117
|
+
setSigningOut(true);
|
|
118
|
+
try {
|
|
119
|
+
await fetch("/api/settings/openai/logout", { method: "POST" });
|
|
120
|
+
updateLoginState({
|
|
121
|
+
phase: "idle",
|
|
122
|
+
loginId: null,
|
|
123
|
+
authUrl: null,
|
|
124
|
+
account: null,
|
|
125
|
+
rateLimits: null,
|
|
126
|
+
error: null,
|
|
127
|
+
startedAt: null,
|
|
128
|
+
updatedAt: new Date().toISOString(),
|
|
129
|
+
});
|
|
130
|
+
setTestResult(null);
|
|
131
|
+
await onChanged();
|
|
132
|
+
} finally {
|
|
133
|
+
setSigningOut(false);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function handleTestConnection() {
|
|
138
|
+
setTesting(true);
|
|
139
|
+
setTestResult(null);
|
|
140
|
+
try {
|
|
141
|
+
const res = await fetch("/api/settings/test", {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: { "Content-Type": "application/json" },
|
|
144
|
+
body: JSON.stringify({ runtime: "openai-codex-app-server" }),
|
|
145
|
+
});
|
|
146
|
+
const result = (await res.json()) as RuntimeConnectionResult;
|
|
147
|
+
setTestResult(result);
|
|
148
|
+
if (result.connected) {
|
|
149
|
+
await onChanged();
|
|
150
|
+
}
|
|
151
|
+
} finally {
|
|
152
|
+
setTesting(false);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const visibleAccount = account ?? loginState.account;
|
|
157
|
+
const visibleRateLimits = rateLimits ?? loginState.rateLimits;
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<div className="space-y-3">
|
|
161
|
+
<p className="text-sm text-muted-foreground">
|
|
162
|
+
ChatGPT mode uses Codex App Server's browser sign-in flow and keeps the session
|
|
163
|
+
in Stagent's isolated Codex home so it does not touch your normal `~/.codex` login.
|
|
164
|
+
</p>
|
|
165
|
+
|
|
166
|
+
{visibleAccount?.email && (
|
|
167
|
+
<div className="rounded-xl border border-border/60 bg-background/40 px-3 py-2">
|
|
168
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
169
|
+
<ShieldCheck className="h-4 w-4 text-success" />
|
|
170
|
+
<span>{visibleAccount.email}</span>
|
|
171
|
+
</div>
|
|
172
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
173
|
+
Plan: {formatPlanType(visibleAccount.planType)}
|
|
174
|
+
</p>
|
|
175
|
+
</div>
|
|
176
|
+
)}
|
|
177
|
+
|
|
178
|
+
{visibleRateLimits?.primary && (
|
|
179
|
+
<div className="rounded-xl border border-border/60 bg-background/40 px-3 py-2">
|
|
180
|
+
<p className="text-sm font-medium">Codex rate limits</p>
|
|
181
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
182
|
+
{visibleRateLimits.primary.usedPercent ?? 0}% used in the current{" "}
|
|
183
|
+
{visibleRateLimits.primary.windowDurationMins ?? "?"}-minute window
|
|
184
|
+
{formatResetAt(visibleRateLimits.primary.resetsAt)
|
|
185
|
+
? ` • resets ${formatResetAt(visibleRateLimits.primary.resetsAt)}`
|
|
186
|
+
: ""}
|
|
187
|
+
</p>
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
{loginState.phase === "pending" ? (
|
|
192
|
+
<div className="rounded-xl border border-primary/20 bg-primary/5 px-3 py-3">
|
|
193
|
+
<div className="flex items-center gap-2 text-sm font-medium text-foreground">
|
|
194
|
+
<Loader2 className="h-4 w-4 animate-spin text-primary" />
|
|
195
|
+
Waiting for ChatGPT sign-in
|
|
196
|
+
</div>
|
|
197
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
198
|
+
Complete the browser flow, then return here. This page will update automatically.
|
|
199
|
+
</p>
|
|
200
|
+
<div className="mt-3 flex flex-wrap items-center gap-2">
|
|
201
|
+
{loginState.authUrl && (
|
|
202
|
+
<Button asChild variant="outline" size="sm">
|
|
203
|
+
<a href={loginState.authUrl} target="_blank" rel="noreferrer">
|
|
204
|
+
Open login page
|
|
205
|
+
<ExternalLink className="ml-1 h-3.5 w-3.5" />
|
|
206
|
+
</a>
|
|
207
|
+
</Button>
|
|
208
|
+
)}
|
|
209
|
+
<Button variant="ghost" size="sm" onClick={handleCancelLogin}>
|
|
210
|
+
Cancel sign-in
|
|
211
|
+
</Button>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
) : (
|
|
215
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
216
|
+
{!connected && (
|
|
217
|
+
<Button size="sm" onClick={handleStartLogin}>
|
|
218
|
+
Sign in with ChatGPT
|
|
219
|
+
</Button>
|
|
220
|
+
)}
|
|
221
|
+
<Button
|
|
222
|
+
variant="outline"
|
|
223
|
+
size="sm"
|
|
224
|
+
onClick={handleTestConnection}
|
|
225
|
+
disabled={testing}
|
|
226
|
+
>
|
|
227
|
+
{testing && <Loader2 className="mr-1 h-3 w-3 animate-spin" />}
|
|
228
|
+
Test connection
|
|
229
|
+
</Button>
|
|
230
|
+
{connected && (
|
|
231
|
+
<Button
|
|
232
|
+
variant="ghost"
|
|
233
|
+
size="sm"
|
|
234
|
+
onClick={handleLogout}
|
|
235
|
+
disabled={signingOut}
|
|
236
|
+
>
|
|
237
|
+
{signingOut ? (
|
|
238
|
+
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
|
|
239
|
+
) : (
|
|
240
|
+
<LogOut className="mr-1 h-3.5 w-3.5" />
|
|
241
|
+
)}
|
|
242
|
+
Sign out
|
|
243
|
+
</Button>
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
|
|
248
|
+
{loginState.phase === "cancelled" && (
|
|
249
|
+
<p className="flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
250
|
+
<CircleSlash className="h-4 w-4" />
|
|
251
|
+
<span>ChatGPT sign-in cancelled.</span>
|
|
252
|
+
</p>
|
|
253
|
+
)}
|
|
254
|
+
|
|
255
|
+
{loginState.phase === "failed" && loginState.error && (
|
|
256
|
+
<p className="flex items-center gap-1.5 text-sm text-status-failed">
|
|
257
|
+
<XCircle className="h-4 w-4" />
|
|
258
|
+
<span>{loginState.error}</span>
|
|
259
|
+
</p>
|
|
260
|
+
)}
|
|
261
|
+
|
|
262
|
+
{testResult && (
|
|
263
|
+
<p
|
|
264
|
+
className={`flex items-center gap-1.5 text-sm ${
|
|
265
|
+
testResult.connected ? "text-success" : "text-status-failed"
|
|
266
|
+
}`}
|
|
267
|
+
>
|
|
268
|
+
{testResult.connected ? (
|
|
269
|
+
<ShieldCheck className="h-4 w-4" />
|
|
270
|
+
) : (
|
|
271
|
+
<RefreshCw className="h-4 w-4" />
|
|
272
|
+
)}
|
|
273
|
+
<span>{testResult.connected ? "Connected" : testResult.error ?? "Connection failed"}</span>
|
|
274
|
+
</p>
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
@@ -13,12 +13,15 @@ import { ApiKeyForm } from "./api-key-form";
|
|
|
13
13
|
import { AuthStatusBadge } from "./auth-status-badge";
|
|
14
14
|
|
|
15
15
|
interface OpenAISettings {
|
|
16
|
+
method: "api_key" | "oauth";
|
|
16
17
|
hasKey: boolean;
|
|
17
18
|
apiKeySource: "db" | "env" | "unknown";
|
|
19
|
+
oauthConnected?: boolean;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export function OpenAIRuntimeSection() {
|
|
21
23
|
const [settings, setSettings] = useState<OpenAISettings>({
|
|
24
|
+
method: "api_key",
|
|
22
25
|
hasKey: false,
|
|
23
26
|
apiKeySource: "unknown",
|
|
24
27
|
});
|
|
@@ -41,7 +44,7 @@ export function OpenAIRuntimeSection() {
|
|
|
41
44
|
const res = await fetch("/api/settings/openai", {
|
|
42
45
|
method: "POST",
|
|
43
46
|
headers: { "Content-Type": "application/json" },
|
|
44
|
-
body: JSON.stringify({ apiKey }),
|
|
47
|
+
body: JSON.stringify({ method: "api_key", apiKey }),
|
|
45
48
|
});
|
|
46
49
|
if (res.ok) {
|
|
47
50
|
const data = (await res.json()) as OpenAISettings;
|
|
@@ -80,6 +83,9 @@ export function OpenAIRuntimeSection() {
|
|
|
80
83
|
<AuthStatusBadge
|
|
81
84
|
connected={connected}
|
|
82
85
|
apiKeySource={settings.apiKeySource}
|
|
86
|
+
authMethod={settings.method}
|
|
87
|
+
oauthConnected={settings.oauthConnected}
|
|
88
|
+
oauthLabel="ChatGPT"
|
|
83
89
|
/>
|
|
84
90
|
</div>
|
|
85
91
|
</CardHeader>
|
|
@@ -17,8 +17,11 @@ import { AuthMethodSelector } from "./auth-method-selector";
|
|
|
17
17
|
import { ApiKeyForm } from "./api-key-form";
|
|
18
18
|
import { AuthStatusBadge } from "./auth-status-badge";
|
|
19
19
|
import { ConnectionTestControl } from "./connection-test-control";
|
|
20
|
+
import { OpenAIChatGPTAuthControl } from "./openai-chatgpt-auth-control";
|
|
20
21
|
import type { AuthMethod, ApiKeySource, RoutingPreference } from "@/lib/constants/settings";
|
|
21
22
|
import type { RuntimeSetupState } from "@/lib/settings/runtime-setup";
|
|
23
|
+
import type { OpenAIAccountInfo, OpenAIRateLimitInfo } from "@/lib/settings/openai-auth";
|
|
24
|
+
import type { OpenAILoginState } from "@/lib/settings/openai-login-manager";
|
|
22
25
|
|
|
23
26
|
// ── Types ────────────────────────────────────────────────────────────
|
|
24
27
|
|
|
@@ -27,6 +30,10 @@ interface ProviderState {
|
|
|
27
30
|
authMethod?: AuthMethod;
|
|
28
31
|
hasKey: boolean;
|
|
29
32
|
apiKeySource: ApiKeySource;
|
|
33
|
+
oauthConnected?: boolean;
|
|
34
|
+
account?: OpenAIAccountInfo | null;
|
|
35
|
+
rateLimits?: OpenAIRateLimitInfo | null;
|
|
36
|
+
login?: OpenAILoginState;
|
|
30
37
|
dualBilling: boolean;
|
|
31
38
|
runtimes: RuntimeSetupState[];
|
|
32
39
|
}
|
|
@@ -99,6 +106,7 @@ const BILLING_LABELS: Record<string, string> = {
|
|
|
99
106
|
|
|
100
107
|
function ProviderRow({
|
|
101
108
|
name,
|
|
109
|
+
oauthLabel,
|
|
102
110
|
provider,
|
|
103
111
|
defaultOpen,
|
|
104
112
|
open: controlledOpen,
|
|
@@ -106,6 +114,7 @@ function ProviderRow({
|
|
|
106
114
|
children,
|
|
107
115
|
}: {
|
|
108
116
|
name: string;
|
|
117
|
+
oauthLabel?: string;
|
|
109
118
|
provider: ProviderState;
|
|
110
119
|
defaultOpen: boolean;
|
|
111
120
|
open?: boolean;
|
|
@@ -125,10 +134,19 @@ function ProviderRow({
|
|
|
125
134
|
const activeRuntimes = provider.runtimes.filter((r) => r.configured);
|
|
126
135
|
const activeCount = activeRuntimes.length;
|
|
127
136
|
const activeLabels = activeRuntimes.map((r) => r.label).join(", ");
|
|
137
|
+
const openAIOAuthPending = provider.authMethod === "oauth" && provider.oauthConnected === false;
|
|
138
|
+
const openAILoginPending = provider.login?.phase === "pending";
|
|
128
139
|
|
|
129
140
|
let statusLine: string;
|
|
130
141
|
if (!provider.configured) {
|
|
131
|
-
statusLine =
|
|
142
|
+
statusLine =
|
|
143
|
+
provider.authMethod === "oauth"
|
|
144
|
+
? "Sign in with ChatGPT to enable Codex App Server"
|
|
145
|
+
: "Add an API key to enable runtimes";
|
|
146
|
+
} else if (openAIOAuthPending && activeCount > 0) {
|
|
147
|
+
statusLine = openAILoginPending
|
|
148
|
+
? `Waiting for ${oauthLabel ?? "OAuth"} sign-in. ${activeLabels} remains active.`
|
|
149
|
+
: `Codex App Server needs ${oauthLabel ?? "OAuth"} sign-in. ${activeLabels} remains active.`;
|
|
132
150
|
} else if (activeCount === 2) {
|
|
133
151
|
statusLine = `2 runtimes active: ${activeLabels}`;
|
|
134
152
|
} else if (activeCount === 1) {
|
|
@@ -157,6 +175,9 @@ function ProviderRow({
|
|
|
157
175
|
<AuthStatusBadge
|
|
158
176
|
connected={provider.configured}
|
|
159
177
|
apiKeySource={provider.apiKeySource}
|
|
178
|
+
authMethod={provider.authMethod}
|
|
179
|
+
oauthLabel={oauthLabel}
|
|
180
|
+
oauthConnected={provider.oauthConnected}
|
|
160
181
|
/>
|
|
161
182
|
</div>
|
|
162
183
|
<p className="text-xs text-muted-foreground mt-0.5">{statusLine}</p>
|
|
@@ -178,8 +199,10 @@ function ProviderRow({
|
|
|
178
199
|
<div className="rounded-xl border border-primary/20 bg-primary/5 px-3 py-2">
|
|
179
200
|
<p className="text-xs text-muted-foreground">
|
|
180
201
|
<span className="font-medium text-foreground">Two billing modes active.</span>{" "}
|
|
181
|
-
|
|
182
|
-
|
|
202
|
+
{name === "Anthropic"
|
|
203
|
+
? "Claude Code uses your Max/Pro subscription. Anthropic Direct API uses pay-as-you-go API billing."
|
|
204
|
+
: "Codex App Server uses your ChatGPT plan. OpenAI Direct uses pay-as-you-go API billing."}{" "}
|
|
205
|
+
Budget guardrails track each separately.
|
|
183
206
|
</p>
|
|
184
207
|
</div>
|
|
185
208
|
)}
|
|
@@ -192,6 +215,14 @@ function ProviderRow({
|
|
|
192
215
|
<div className="grid gap-2 sm:grid-cols-2">
|
|
193
216
|
{provider.runtimes.map((runtime) => {
|
|
194
217
|
const isActive = runtime.configured;
|
|
218
|
+
const inactiveDescription = runtime.runtimeId.includes("direct")
|
|
219
|
+
? "Requires API key"
|
|
220
|
+
: runtime.runtimeId === "openai-codex-app-server" &&
|
|
221
|
+
provider.authMethod === "oauth"
|
|
222
|
+
? provider.login?.phase === "pending"
|
|
223
|
+
? "Waiting for ChatGPT sign-in"
|
|
224
|
+
: "Sign in with ChatGPT"
|
|
225
|
+
: "Requires CLI or API key";
|
|
195
226
|
return (
|
|
196
227
|
<div
|
|
197
228
|
key={runtime.runtimeId}
|
|
@@ -212,9 +243,7 @@ function ProviderRow({
|
|
|
212
243
|
<p className="text-xs text-muted-foreground mt-0.5">
|
|
213
244
|
{isActive
|
|
214
245
|
? (RUNTIME_DESCRIPTIONS[runtime.runtimeId] ?? "Active")
|
|
215
|
-
:
|
|
216
|
-
? "Requires API key"
|
|
217
|
-
: "Requires CLI or API key"}
|
|
246
|
+
: inactiveDescription}
|
|
218
247
|
</p>
|
|
219
248
|
</div>
|
|
220
249
|
);
|
|
@@ -233,6 +262,7 @@ export function ProvidersAndRuntimesSection() {
|
|
|
233
262
|
const [data, setData] = useState<ProvidersPayload | null>(null);
|
|
234
263
|
const [loading, setLoading] = useState(true);
|
|
235
264
|
const [anthropicOpen, setAnthropicOpen] = useState(false);
|
|
265
|
+
const [openAILoginState, setOpenAILoginState] = useState<OpenAILoginState | null>(null);
|
|
236
266
|
|
|
237
267
|
const fetchData = useCallback(async () => {
|
|
238
268
|
try {
|
|
@@ -256,6 +286,7 @@ export function ProvidersAndRuntimesSection() {
|
|
|
256
286
|
if (none || !data.providers.anthropic.configured) {
|
|
257
287
|
setAnthropicOpen(true);
|
|
258
288
|
}
|
|
289
|
+
setOpenAILoginState(data.providers.openai.login ?? null);
|
|
259
290
|
}
|
|
260
291
|
}, [data?.configuredProviderCount, data?.providers.anthropic.configured]);
|
|
261
292
|
|
|
@@ -292,7 +323,16 @@ export function ProvidersAndRuntimesSection() {
|
|
|
292
323
|
const res = await fetch("/api/settings/openai", {
|
|
293
324
|
method: "POST",
|
|
294
325
|
headers: { "Content-Type": "application/json" },
|
|
295
|
-
body: JSON.stringify({ apiKey }),
|
|
326
|
+
body: JSON.stringify({ method: "api_key", apiKey }),
|
|
327
|
+
});
|
|
328
|
+
if (res.ok) fetchData();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function handleOpenAIMethodChange(method: AuthMethod) {
|
|
332
|
+
const res = await fetch("/api/settings/openai", {
|
|
333
|
+
method: "POST",
|
|
334
|
+
headers: { "Content-Type": "application/json" },
|
|
335
|
+
body: JSON.stringify({ method }),
|
|
296
336
|
});
|
|
297
337
|
if (res.ok) fetchData();
|
|
298
338
|
}
|
|
@@ -308,6 +348,17 @@ export function ProvidersAndRuntimesSection() {
|
|
|
308
348
|
return result;
|
|
309
349
|
}
|
|
310
350
|
|
|
351
|
+
async function handleOpenAIDirectTest() {
|
|
352
|
+
const res = await fetch("/api/settings/test", {
|
|
353
|
+
method: "POST",
|
|
354
|
+
headers: { "Content-Type": "application/json" },
|
|
355
|
+
body: JSON.stringify({ runtime: "openai-direct" }),
|
|
356
|
+
});
|
|
357
|
+
const result = await res.json();
|
|
358
|
+
fetchData();
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
|
|
311
362
|
// ── Routing preference handler ───────────────────────────────────
|
|
312
363
|
|
|
313
364
|
async function handleRoutingChange(value: RoutingPreference) {
|
|
@@ -349,6 +400,10 @@ export function ProvidersAndRuntimesSection() {
|
|
|
349
400
|
}
|
|
350
401
|
|
|
351
402
|
const { providers, routingPreference, configuredProviderCount } = data;
|
|
403
|
+
const openAIProvider: ProviderState = {
|
|
404
|
+
...providers.openai,
|
|
405
|
+
login: openAILoginState ?? providers.openai.login,
|
|
406
|
+
};
|
|
352
407
|
const noneConfigured = configuredProviderCount === 0;
|
|
353
408
|
const recommendedAuth = recommendedAuthForRouting(routingPreference);
|
|
354
409
|
|
|
@@ -430,7 +485,7 @@ export function ProvidersAndRuntimesSection() {
|
|
|
430
485
|
<p className="text-xs text-primary/70">
|
|
431
486
|
{recommendedAuth === "api_key"
|
|
432
487
|
? "This preference works best with an API key configured below."
|
|
433
|
-
: "This preference works well with
|
|
488
|
+
: "This preference works well with subscription-backed auth configured below."}
|
|
434
489
|
</p>
|
|
435
490
|
)}
|
|
436
491
|
</div>
|
|
@@ -440,6 +495,7 @@ export function ProvidersAndRuntimesSection() {
|
|
|
440
495
|
{/* Anthropic provider — controlled open state */}
|
|
441
496
|
<ProviderRow
|
|
442
497
|
name="Anthropic"
|
|
498
|
+
oauthLabel="Claude Max/Pro"
|
|
443
499
|
provider={providers.anthropic}
|
|
444
500
|
defaultOpen={false}
|
|
445
501
|
open={anthropicOpen}
|
|
@@ -479,19 +535,82 @@ export function ProvidersAndRuntimesSection() {
|
|
|
479
535
|
{/* OpenAI provider — uncontrolled */}
|
|
480
536
|
<ProviderRow
|
|
481
537
|
name="OpenAI"
|
|
482
|
-
|
|
483
|
-
|
|
538
|
+
oauthLabel="ChatGPT"
|
|
539
|
+
provider={openAIProvider}
|
|
540
|
+
defaultOpen={
|
|
541
|
+
noneConfigured ||
|
|
542
|
+
!openAIProvider.configured ||
|
|
543
|
+
((openAIProvider.authMethod ?? "api_key") === "oauth" &&
|
|
544
|
+
!(openAIProvider.oauthConnected ?? false))
|
|
545
|
+
}
|
|
484
546
|
>
|
|
485
|
-
<
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
547
|
+
<AuthMethodSelector
|
|
548
|
+
value={openAIProvider.authMethod ?? "api_key"}
|
|
549
|
+
onChange={handleOpenAIMethodChange}
|
|
550
|
+
recommendedMethod={recommendedAuth}
|
|
551
|
+
label="Codex App Server Authentication"
|
|
552
|
+
options={[
|
|
553
|
+
{
|
|
554
|
+
id: "api_key",
|
|
555
|
+
icon: Zap,
|
|
556
|
+
title: "API Key",
|
|
557
|
+
description: "Use an OpenAI API key for Codex App Server",
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
id: "oauth",
|
|
561
|
+
icon: Crown,
|
|
562
|
+
title: "ChatGPT",
|
|
563
|
+
description: "Use your ChatGPT plan with browser sign-in",
|
|
564
|
+
},
|
|
565
|
+
]}
|
|
494
566
|
/>
|
|
567
|
+
|
|
568
|
+
{(openAIProvider.authMethod ?? "api_key") === "oauth" ? (
|
|
569
|
+
<OpenAIChatGPTAuthControl
|
|
570
|
+
connected={openAIProvider.oauthConnected ?? false}
|
|
571
|
+
account={openAIProvider.account ?? null}
|
|
572
|
+
rateLimits={openAIProvider.rateLimits ?? null}
|
|
573
|
+
initialLoginState={
|
|
574
|
+
openAIProvider.login ?? {
|
|
575
|
+
phase: "idle",
|
|
576
|
+
loginId: null,
|
|
577
|
+
authUrl: null,
|
|
578
|
+
account: null,
|
|
579
|
+
rateLimits: null,
|
|
580
|
+
error: null,
|
|
581
|
+
startedAt: null,
|
|
582
|
+
updatedAt: new Date().toISOString(),
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
onChanged={fetchData}
|
|
586
|
+
onLoginStateChange={setOpenAILoginState}
|
|
587
|
+
/>
|
|
588
|
+
) : (
|
|
589
|
+
<p className="text-sm text-muted-foreground">
|
|
590
|
+
API key mode authenticates Codex App Server directly and also powers OpenAI Direct.
|
|
591
|
+
</p>
|
|
592
|
+
)}
|
|
593
|
+
|
|
594
|
+
<Separator />
|
|
595
|
+
|
|
596
|
+
<div className="space-y-4">
|
|
597
|
+
<div>
|
|
598
|
+
<p className="text-sm font-medium">OpenAI Direct API Key</p>
|
|
599
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
600
|
+
Used by the OpenAI Direct runtime. If Codex App Server is in API key mode, it shares this key.
|
|
601
|
+
</p>
|
|
602
|
+
</div>
|
|
603
|
+
<ApiKeyForm
|
|
604
|
+
hasKey={providers.openai.hasKey}
|
|
605
|
+
onSave={handleOpenAISaveKey}
|
|
606
|
+
onTest={handleOpenAIDirectTest}
|
|
607
|
+
keyPrefix="sk-"
|
|
608
|
+
placeholder="sk-..."
|
|
609
|
+
maskedPrefix="sk-••••••"
|
|
610
|
+
envVarName="OPENAI_API_KEY"
|
|
611
|
+
testButtonLabel="Test OpenAI Direct"
|
|
612
|
+
/>
|
|
613
|
+
</div>
|
|
495
614
|
</ProviderRow>
|
|
496
615
|
</CardContent>
|
|
497
616
|
</Card>
|