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
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { Check, ShieldCheck, X } from "lucide-react";
|
|
4
|
+
import { Check, Send, ShieldCheck, X } from "lucide-react";
|
|
5
5
|
import { toast } from "sonner";
|
|
6
6
|
|
|
7
7
|
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { Textarea } from "@/components/ui/textarea";
|
|
8
9
|
import { cn } from "@/lib/utils";
|
|
9
10
|
import {
|
|
10
11
|
buildPermissionPattern,
|
|
@@ -12,6 +13,28 @@ import {
|
|
|
12
13
|
type PermissionToolInput,
|
|
13
14
|
} from "@/lib/notifications/permissions";
|
|
14
15
|
|
|
16
|
+
interface AskUserQuestionOption {
|
|
17
|
+
label: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseQuestionOptions(toolInput: PermissionToolInput): AskUserQuestionOption[] {
|
|
22
|
+
const raw = (toolInput as { options?: unknown }).options;
|
|
23
|
+
if (!Array.isArray(raw)) return [];
|
|
24
|
+
const out: AskUserQuestionOption[] = [];
|
|
25
|
+
for (const item of raw) {
|
|
26
|
+
if (item && typeof item === "object" && typeof (item as { label?: unknown }).label === "string") {
|
|
27
|
+
const entry: AskUserQuestionOption = {
|
|
28
|
+
label: (item as { label: string }).label,
|
|
29
|
+
};
|
|
30
|
+
const desc = (item as { description?: unknown }).description;
|
|
31
|
+
if (typeof desc === "string") entry.description = desc;
|
|
32
|
+
out.push(entry);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
interface PermissionResponseActionsProps {
|
|
16
39
|
taskId?: string | null;
|
|
17
40
|
notificationId: string;
|
|
@@ -87,6 +110,19 @@ export function PermissionResponseActions({
|
|
|
87
110
|
}
|
|
88
111
|
}
|
|
89
112
|
|
|
113
|
+
if (toolName === "AskUserQuestion" || toolName === "ask_user_question") {
|
|
114
|
+
return (
|
|
115
|
+
<QuestionReplyActions
|
|
116
|
+
taskId={taskId}
|
|
117
|
+
notificationId={notificationId}
|
|
118
|
+
toolInput={toolInput}
|
|
119
|
+
onResponded={onResponded}
|
|
120
|
+
className={className}
|
|
121
|
+
buttonSize={buttonSize}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
90
126
|
return (
|
|
91
127
|
<div
|
|
92
128
|
className={cn(
|
|
@@ -124,3 +160,121 @@ export function PermissionResponseActions({
|
|
|
124
160
|
</div>
|
|
125
161
|
);
|
|
126
162
|
}
|
|
163
|
+
|
|
164
|
+
interface QuestionReplyActionsProps {
|
|
165
|
+
taskId?: string | null;
|
|
166
|
+
notificationId: string;
|
|
167
|
+
toolInput: PermissionToolInput;
|
|
168
|
+
onResponded?: () => void;
|
|
169
|
+
className?: string;
|
|
170
|
+
buttonSize?: "sm" | "default";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Renders the response UI for an `AskUserQuestion` notification:
|
|
175
|
+
* - If `toolInput.options` is a non-empty array → card-cluster radiogroup (one click = answer).
|
|
176
|
+
* - Otherwise → free-form textarea + Send.
|
|
177
|
+
*
|
|
178
|
+
* Posts to /api/tasks/[id]/respond with `{ behavior: "allow", updatedInput: { answer } }`.
|
|
179
|
+
* The task runtime's `waitForToolPermissionResponse()` unblocks and returns `{ answer }`
|
|
180
|
+
* to the agent.
|
|
181
|
+
*/
|
|
182
|
+
function QuestionReplyActions({
|
|
183
|
+
taskId,
|
|
184
|
+
notificationId,
|
|
185
|
+
toolInput,
|
|
186
|
+
onResponded,
|
|
187
|
+
className,
|
|
188
|
+
buttonSize = "sm",
|
|
189
|
+
}: QuestionReplyActionsProps) {
|
|
190
|
+
const [loading, setLoading] = useState(false);
|
|
191
|
+
const [draft, setDraft] = useState("");
|
|
192
|
+
const options = parseQuestionOptions(toolInput);
|
|
193
|
+
|
|
194
|
+
async function sendAnswer(answer: string) {
|
|
195
|
+
if (!answer.trim()) return;
|
|
196
|
+
setLoading(true);
|
|
197
|
+
try {
|
|
198
|
+
const res = await fetch(`/api/tasks/${taskId ?? "_checkpoint"}/respond`, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
headers: { "Content-Type": "application/json" },
|
|
201
|
+
body: JSON.stringify({
|
|
202
|
+
notificationId,
|
|
203
|
+
behavior: "allow",
|
|
204
|
+
updatedInput: { answer: answer.trim() },
|
|
205
|
+
}),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (!res.ok) {
|
|
209
|
+
const data = (await res.json().catch(() => null)) as
|
|
210
|
+
| { error?: string }
|
|
211
|
+
| null;
|
|
212
|
+
throw new Error(data?.error ?? "Failed to send answer");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
onResponded?.();
|
|
216
|
+
} catch (error) {
|
|
217
|
+
toast.error(
|
|
218
|
+
error instanceof Error ? error.message : "Failed to send answer"
|
|
219
|
+
);
|
|
220
|
+
} finally {
|
|
221
|
+
setLoading(false);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (options.length > 0) {
|
|
226
|
+
return (
|
|
227
|
+
<div
|
|
228
|
+
role="radiogroup"
|
|
229
|
+
aria-label="Choose a response"
|
|
230
|
+
className={cn("grid gap-2 sm:grid-cols-1", className)}
|
|
231
|
+
>
|
|
232
|
+
{options.map((option) => (
|
|
233
|
+
<button
|
|
234
|
+
key={option.label}
|
|
235
|
+
type="button"
|
|
236
|
+
role="radio"
|
|
237
|
+
aria-checked={false}
|
|
238
|
+
onClick={() => sendAnswer(option.label)}
|
|
239
|
+
disabled={loading}
|
|
240
|
+
className="rounded-lg border border-border/60 bg-background/60 p-3 text-left transition-colors hover:bg-accent/40 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none disabled:opacity-60"
|
|
241
|
+
>
|
|
242
|
+
<div className="text-sm font-medium text-foreground">{option.label}</div>
|
|
243
|
+
{option.description && (
|
|
244
|
+
<div className="mt-1 text-xs text-muted-foreground">{option.description}</div>
|
|
245
|
+
)}
|
|
246
|
+
</button>
|
|
247
|
+
))}
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<div className={cn("flex flex-col gap-2", className)}>
|
|
254
|
+
<Textarea
|
|
255
|
+
value={draft}
|
|
256
|
+
onChange={(e) => setDraft(e.target.value)}
|
|
257
|
+
placeholder="Type your reply…"
|
|
258
|
+
rows={3}
|
|
259
|
+
disabled={loading}
|
|
260
|
+
onKeyDown={(e) => {
|
|
261
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
262
|
+
e.preventDefault();
|
|
263
|
+
sendAnswer(draft);
|
|
264
|
+
}
|
|
265
|
+
}}
|
|
266
|
+
/>
|
|
267
|
+
<div className="flex items-center justify-between gap-2">
|
|
268
|
+
<span className="text-xs text-muted-foreground">⌘/Ctrl + Enter to send</span>
|
|
269
|
+
<Button
|
|
270
|
+
size={buttonSize}
|
|
271
|
+
onClick={() => sendAnswer(draft)}
|
|
272
|
+
disabled={loading || !draft.trim()}
|
|
273
|
+
>
|
|
274
|
+
<Send className="h-3.5 w-3.5" />
|
|
275
|
+
Send
|
|
276
|
+
</Button>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { Calendar } from "lucide-react";
|
|
12
12
|
import { toast } from "sonner";
|
|
13
13
|
import { ScheduleForm, type ScheduleFormValues } from "./schedule-form";
|
|
14
|
+
import type { CronCollisionWarning } from "@/lib/schedules/collision-check";
|
|
14
15
|
|
|
15
16
|
interface ScheduleCreateSheetProps {
|
|
16
17
|
projects: { id: string; name: string }[];
|
|
@@ -27,6 +28,7 @@ export function ScheduleCreateSheet({
|
|
|
27
28
|
}: ScheduleCreateSheetProps) {
|
|
28
29
|
const [loading, setLoading] = useState(false);
|
|
29
30
|
const [error, setError] = useState<string | null>(null);
|
|
31
|
+
const [warnings, setWarnings] = useState<CronCollisionWarning[]>([]);
|
|
30
32
|
|
|
31
33
|
async function handleSubmit(values: ScheduleFormValues) {
|
|
32
34
|
setLoading(true);
|
|
@@ -58,10 +60,15 @@ export function ScheduleCreateSheet({
|
|
|
58
60
|
});
|
|
59
61
|
|
|
60
62
|
if (res.ok) {
|
|
63
|
+
const { warnings: newWarnings } = await res.json();
|
|
61
64
|
setError(null);
|
|
62
|
-
|
|
65
|
+
setWarnings(newWarnings ?? []);
|
|
63
66
|
toast.success("Schedule created");
|
|
64
67
|
onCreated();
|
|
68
|
+
if (!newWarnings || newWarnings.length === 0) {
|
|
69
|
+
onOpenChange(false);
|
|
70
|
+
}
|
|
71
|
+
// Keep sheet open if there are warnings so the user sees the banner
|
|
65
72
|
} else {
|
|
66
73
|
const data = await res.json().catch(() => null);
|
|
67
74
|
setError(data?.error ?? `Failed to create schedule (${res.status})`);
|
|
@@ -89,6 +96,17 @@ export function ScheduleCreateSheet({
|
|
|
89
96
|
|
|
90
97
|
{/* Body — px-6 pb-6 per project convention (SheetContent has NO body padding) */}
|
|
91
98
|
<div className="px-6 pb-6 overflow-y-auto">
|
|
99
|
+
{warnings.length > 0 && (
|
|
100
|
+
<div className="mb-4 rounded-lg border border-amber-500/40 bg-amber-50 p-3 text-sm">
|
|
101
|
+
<p className="font-medium text-amber-900">
|
|
102
|
+
Overlap detected with: {warnings[0].overlappingSchedules.join(", ")}
|
|
103
|
+
</p>
|
|
104
|
+
<p className="text-amber-800">
|
|
105
|
+
Combined load: ~{warnings[0].estimatedConcurrentSteps} agent steps.
|
|
106
|
+
Schedules will take turns; the last to run may be delayed.
|
|
107
|
+
</p>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
92
110
|
<ScheduleForm
|
|
93
111
|
projects={projects}
|
|
94
112
|
onSubmit={handleSubmit}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type ScheduleFormValues,
|
|
17
17
|
type ScheduleFormInitialValues,
|
|
18
18
|
} from "./schedule-form";
|
|
19
|
+
import type { CronCollisionWarning } from "@/lib/schedules/collision-check";
|
|
19
20
|
|
|
20
21
|
interface ScheduleEditSheetProps {
|
|
21
22
|
scheduleId: string | null;
|
|
@@ -49,6 +50,7 @@ export function ScheduleEditSheet({
|
|
|
49
50
|
const [loaded, setLoaded] = useState(false);
|
|
50
51
|
const [loading, setLoading] = useState(false);
|
|
51
52
|
const [error, setError] = useState<string | null>(null);
|
|
53
|
+
const [warnings, setWarnings] = useState<CronCollisionWarning[]>([]);
|
|
52
54
|
|
|
53
55
|
const fetchSchedule = useCallback(async () => {
|
|
54
56
|
if (!scheduleId) return;
|
|
@@ -63,6 +65,7 @@ export function ScheduleEditSheet({
|
|
|
63
65
|
setSchedule(null);
|
|
64
66
|
setLoaded(false);
|
|
65
67
|
setError(null);
|
|
68
|
+
setWarnings([]);
|
|
66
69
|
return;
|
|
67
70
|
}
|
|
68
71
|
fetchSchedule();
|
|
@@ -102,9 +105,14 @@ export function ScheduleEditSheet({
|
|
|
102
105
|
});
|
|
103
106
|
|
|
104
107
|
if (res.ok) {
|
|
108
|
+
const { warnings: newWarnings } = await res.json();
|
|
109
|
+
setWarnings(newWarnings ?? []);
|
|
105
110
|
toast.success("Schedule updated");
|
|
106
|
-
onOpenChange(false);
|
|
107
111
|
onUpdated();
|
|
112
|
+
if (!newWarnings || newWarnings.length === 0) {
|
|
113
|
+
onOpenChange(false);
|
|
114
|
+
}
|
|
115
|
+
// Keep sheet open if there are warnings so the user sees the banner
|
|
108
116
|
} else {
|
|
109
117
|
const data = await res.json().catch(() => null);
|
|
110
118
|
setError(data?.error ?? `Failed to update schedule (${res.status})`);
|
|
@@ -131,6 +139,17 @@ export function ScheduleEditSheet({
|
|
|
131
139
|
|
|
132
140
|
{/* Body — px-6 pb-6 per project convention (SheetContent has NO body padding) */}
|
|
133
141
|
<div className="px-6 pb-6 overflow-y-auto">
|
|
142
|
+
{warnings.length > 0 && (
|
|
143
|
+
<div className="mb-4 rounded-lg border border-amber-500/40 bg-amber-50 p-3 text-sm">
|
|
144
|
+
<p className="font-medium text-amber-900">
|
|
145
|
+
Overlap detected with: {warnings[0].overlappingSchedules.join(", ")}
|
|
146
|
+
</p>
|
|
147
|
+
<p className="text-amber-800">
|
|
148
|
+
Combined load: ~{warnings[0].estimatedConcurrentSteps} agent steps.
|
|
149
|
+
Schedules will take turns; the last to run may be delayed.
|
|
150
|
+
</p>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
134
153
|
{!loaded ? (
|
|
135
154
|
<div className="space-y-4">
|
|
136
155
|
<Skeleton className="h-8 w-full" />
|
|
@@ -63,6 +63,7 @@ export interface ScheduleFormValues {
|
|
|
63
63
|
activeTimezone: string;
|
|
64
64
|
heartbeatBudgetPerDay: number | "";
|
|
65
65
|
documentIds: string[];
|
|
66
|
+
maxTurns: number | null;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
export interface ScheduleFormInitialValues {
|
|
@@ -76,6 +77,7 @@ export interface ScheduleFormInitialValues {
|
|
|
76
77
|
recurs: boolean;
|
|
77
78
|
maxFirings: number | null;
|
|
78
79
|
expiresAt: string | null;
|
|
80
|
+
maxTurns?: number | null;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
interface ScheduleFormProps {
|
|
@@ -140,6 +142,7 @@ export function ScheduleForm({
|
|
|
140
142
|
const [expiresInHours, setExpiresInHours] = useState<number | "">(
|
|
141
143
|
initialValues ? "" : ""
|
|
142
144
|
);
|
|
145
|
+
const [maxTurns, setMaxTurns] = useState<number | null>(initialValues?.maxTurns ?? null);
|
|
143
146
|
const [profiles, setProfiles] = useState<ProfileOption[]>([]);
|
|
144
147
|
|
|
145
148
|
// NL schedule input state
|
|
@@ -279,6 +282,7 @@ export function ScheduleForm({
|
|
|
279
282
|
activeTimezone,
|
|
280
283
|
heartbeatBudgetPerDay,
|
|
281
284
|
documentIds: [...selectedDocIds],
|
|
285
|
+
maxTurns,
|
|
282
286
|
});
|
|
283
287
|
}
|
|
284
288
|
|
|
@@ -507,6 +511,13 @@ export function ScheduleForm({
|
|
|
507
511
|
? "Extra instructions appended to the heartbeat evaluation"
|
|
508
512
|
: "Instructions for each execution"}
|
|
509
513
|
</p>
|
|
514
|
+
{scheduleType === "scheduled" && (
|
|
515
|
+
<p className="text-muted-foreground text-xs">
|
|
516
|
+
Note: writing "MAX N turns" in your prompt is a hint to the model,
|
|
517
|
+
not a runtime limit. Use <strong>Max agent steps</strong> below to enforce
|
|
518
|
+
a budget.
|
|
519
|
+
</p>
|
|
520
|
+
)}
|
|
510
521
|
</div>
|
|
511
522
|
|
|
512
523
|
{/* Natural Language Schedule Input */}
|
|
@@ -640,6 +651,26 @@ export function ScheduleForm({
|
|
|
640
651
|
)}
|
|
641
652
|
</div>
|
|
642
653
|
|
|
654
|
+
{/* Max agent steps */}
|
|
655
|
+
<div className="space-y-2">
|
|
656
|
+
<Label htmlFor="max-turns">Max agent steps per run</Label>
|
|
657
|
+
<Input
|
|
658
|
+
id="max-turns"
|
|
659
|
+
type="number"
|
|
660
|
+
min={1}
|
|
661
|
+
max={10000}
|
|
662
|
+
placeholder="Inherits global default"
|
|
663
|
+
value={maxTurns ?? ""}
|
|
664
|
+
onChange={(e) =>
|
|
665
|
+
setMaxTurns(e.target.value ? parseInt(e.target.value, 10) : null)
|
|
666
|
+
}
|
|
667
|
+
/>
|
|
668
|
+
<p className="text-muted-foreground text-xs">
|
|
669
|
+
One step = one agent action (message, tool call, or sub-response). Most
|
|
670
|
+
schedules use 50–500 steps; heavy research runs 2,000+.
|
|
671
|
+
</p>
|
|
672
|
+
</div>
|
|
673
|
+
|
|
643
674
|
{/* Project */}
|
|
644
675
|
{projects.length > 0 && (
|
|
645
676
|
<div className="space-y-2">
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { ProvidersAndRuntimesSection } from "@/components/settings/providers-runtimes-section";
|
|
5
|
+
|
|
6
|
+
describe("providers and runtimes section", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
vi.clearAllMocks();
|
|
9
|
+
vi.stubGlobal("open", vi.fn());
|
|
10
|
+
vi.stubGlobal(
|
|
11
|
+
"fetch",
|
|
12
|
+
vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
13
|
+
const url = String(input);
|
|
14
|
+
const method = init?.method ?? "GET";
|
|
15
|
+
|
|
16
|
+
if (url === "/api/settings/providers" && method === "GET") {
|
|
17
|
+
return {
|
|
18
|
+
ok: true,
|
|
19
|
+
json: async () => ({
|
|
20
|
+
providers: {
|
|
21
|
+
anthropic: {
|
|
22
|
+
configured: false,
|
|
23
|
+
authMethod: "api_key",
|
|
24
|
+
hasKey: false,
|
|
25
|
+
apiKeySource: "unknown",
|
|
26
|
+
dualBilling: false,
|
|
27
|
+
runtimes: [
|
|
28
|
+
{
|
|
29
|
+
runtimeId: "claude-code",
|
|
30
|
+
label: "Claude Code",
|
|
31
|
+
providerId: "anthropic",
|
|
32
|
+
configured: false,
|
|
33
|
+
authMethod: "none",
|
|
34
|
+
apiKeySource: "unknown",
|
|
35
|
+
billingMode: "usage",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
runtimeId: "anthropic-direct",
|
|
39
|
+
label: "Anthropic Direct API",
|
|
40
|
+
providerId: "anthropic",
|
|
41
|
+
configured: false,
|
|
42
|
+
authMethod: "none",
|
|
43
|
+
apiKeySource: "unknown",
|
|
44
|
+
billingMode: "usage",
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
openai: {
|
|
49
|
+
configured: true,
|
|
50
|
+
authMethod: "oauth",
|
|
51
|
+
hasKey: true,
|
|
52
|
+
apiKeySource: "env",
|
|
53
|
+
oauthConnected: false,
|
|
54
|
+
account: null,
|
|
55
|
+
rateLimits: null,
|
|
56
|
+
login: {
|
|
57
|
+
phase: "idle",
|
|
58
|
+
loginId: null,
|
|
59
|
+
authUrl: null,
|
|
60
|
+
account: null,
|
|
61
|
+
rateLimits: null,
|
|
62
|
+
error: null,
|
|
63
|
+
startedAt: null,
|
|
64
|
+
updatedAt: new Date("2026-04-10T15:00:00.000Z").toISOString(),
|
|
65
|
+
},
|
|
66
|
+
dualBilling: false,
|
|
67
|
+
runtimes: [
|
|
68
|
+
{
|
|
69
|
+
runtimeId: "openai-codex-app-server",
|
|
70
|
+
label: "OpenAI Codex App Server",
|
|
71
|
+
providerId: "openai",
|
|
72
|
+
configured: false,
|
|
73
|
+
authMethod: "oauth",
|
|
74
|
+
apiKeySource: "oauth",
|
|
75
|
+
billingMode: "usage",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
runtimeId: "openai-direct",
|
|
79
|
+
label: "OpenAI Direct API",
|
|
80
|
+
providerId: "openai",
|
|
81
|
+
configured: true,
|
|
82
|
+
authMethod: "api_key",
|
|
83
|
+
apiKeySource: "env",
|
|
84
|
+
billingMode: "usage",
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
routingPreference: "quality",
|
|
90
|
+
configuredProviderCount: 1,
|
|
91
|
+
}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (url === "/api/settings/openai/login" && method === "POST") {
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
json: async () => ({
|
|
99
|
+
phase: "pending",
|
|
100
|
+
loginId: "login-1",
|
|
101
|
+
authUrl: "https://auth.openai.com/log-in",
|
|
102
|
+
account: null,
|
|
103
|
+
rateLimits: null,
|
|
104
|
+
error: null,
|
|
105
|
+
startedAt: new Date("2026-04-10T15:01:00.000Z").toISOString(),
|
|
106
|
+
updatedAt: new Date("2026-04-10T15:01:00.000Z").toISOString(),
|
|
107
|
+
}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
afterEach(() => {
|
|
117
|
+
vi.unstubAllGlobals();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("shows partial OpenAI setup state when ChatGPT auth is selected but not connected", async () => {
|
|
121
|
+
render(<ProvidersAndRuntimesSection />);
|
|
122
|
+
|
|
123
|
+
await waitFor(() => {
|
|
124
|
+
expect(screen.getByText("Direct API only")).toBeInTheDocument();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(
|
|
128
|
+
screen.getByText("Codex App Server needs ChatGPT sign-in. OpenAI Direct API remains active.")
|
|
129
|
+
).toBeInTheDocument();
|
|
130
|
+
expect(screen.getAllByText("Sign in with ChatGPT")).toHaveLength(2);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("updates the provider row immediately when ChatGPT sign-in starts", async () => {
|
|
134
|
+
render(<ProvidersAndRuntimesSection />);
|
|
135
|
+
|
|
136
|
+
const signInButton = await screen.findByRole("button", {
|
|
137
|
+
name: "Sign in with ChatGPT",
|
|
138
|
+
});
|
|
139
|
+
signInButton.click();
|
|
140
|
+
|
|
141
|
+
await waitFor(() => {
|
|
142
|
+
expect(
|
|
143
|
+
screen.getByText("Waiting for ChatGPT sign-in. OpenAI Direct API remains active.")
|
|
144
|
+
).toBeInTheDocument();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(screen.getAllByText("Waiting for ChatGPT sign-in")).toHaveLength(2);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -4,13 +4,22 @@ import { Key, Shield } from "lucide-react";
|
|
|
4
4
|
import { cn } from "@/lib/utils";
|
|
5
5
|
import type { AuthMethod } from "@/lib/constants/settings";
|
|
6
6
|
|
|
7
|
+
interface AuthMethodOption {
|
|
8
|
+
id: AuthMethod;
|
|
9
|
+
icon: typeof Key;
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
interface AuthMethodSelectorProps {
|
|
8
15
|
value: AuthMethod;
|
|
9
16
|
onChange: (method: AuthMethod) => void;
|
|
10
17
|
recommendedMethod?: AuthMethod | null;
|
|
18
|
+
label?: string;
|
|
19
|
+
options?: AuthMethodOption[];
|
|
11
20
|
}
|
|
12
21
|
|
|
13
|
-
const
|
|
22
|
+
const defaultMethods = [
|
|
14
23
|
{
|
|
15
24
|
id: "api_key" as const,
|
|
16
25
|
icon: Key,
|
|
@@ -25,12 +34,18 @@ const methods = [
|
|
|
25
34
|
},
|
|
26
35
|
];
|
|
27
36
|
|
|
28
|
-
export function AuthMethodSelector({
|
|
37
|
+
export function AuthMethodSelector({
|
|
38
|
+
value,
|
|
39
|
+
onChange,
|
|
40
|
+
recommendedMethod,
|
|
41
|
+
label = "Authentication Method",
|
|
42
|
+
options = defaultMethods,
|
|
43
|
+
}: AuthMethodSelectorProps) {
|
|
29
44
|
return (
|
|
30
45
|
<div className="space-y-2">
|
|
31
|
-
<p className="text-sm font-medium">
|
|
46
|
+
<p className="text-sm font-medium">{label}</p>
|
|
32
47
|
<div className="grid grid-cols-2 gap-3">
|
|
33
|
-
{
|
|
48
|
+
{options.map((method) => {
|
|
34
49
|
const Icon = method.icon;
|
|
35
50
|
const isSelected = value === method.id;
|
|
36
51
|
return (
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Badge } from "@/components/ui/badge";
|
|
4
|
-
import type { ApiKeySource } from "@/lib/constants/settings";
|
|
4
|
+
import type { ApiKeySource, AuthMethod } from "@/lib/constants/settings";
|
|
5
5
|
|
|
6
6
|
interface AuthStatusBadgeProps {
|
|
7
7
|
connected: boolean;
|
|
8
8
|
apiKeySource: ApiKeySource;
|
|
9
|
+
authMethod?: AuthMethod | "none";
|
|
10
|
+
oauthLabel?: string;
|
|
11
|
+
oauthConnected?: boolean;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
const sourceLabels: Record<ApiKeySource, string> = {
|
|
12
15
|
db: "Managed API Key",
|
|
13
16
|
env: "Environment Variable",
|
|
14
|
-
oauth: "OAuth
|
|
17
|
+
oauth: "OAuth",
|
|
15
18
|
unknown: "Unknown",
|
|
16
19
|
};
|
|
17
20
|
|
|
18
|
-
export function AuthStatusBadge({
|
|
21
|
+
export function AuthStatusBadge({
|
|
22
|
+
connected,
|
|
23
|
+
apiKeySource,
|
|
24
|
+
authMethod,
|
|
25
|
+
oauthLabel = "OAuth",
|
|
26
|
+
oauthConnected,
|
|
27
|
+
}: AuthStatusBadgeProps) {
|
|
19
28
|
if (!connected && apiKeySource === "unknown") {
|
|
20
29
|
return (
|
|
21
30
|
<Badge variant="outline" className="border-warning/50 text-warning">
|
|
@@ -32,6 +41,22 @@ export function AuthStatusBadge({ connected, apiKeySource }: AuthStatusBadgeProp
|
|
|
32
41
|
);
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
if (connected && authMethod === "oauth" && oauthConnected === false) {
|
|
45
|
+
return (
|
|
46
|
+
<Badge variant="outline" className="border-status-warning/50 text-status-warning">
|
|
47
|
+
Direct API only
|
|
48
|
+
</Badge>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (connected && authMethod === "oauth" && (oauthConnected ?? true)) {
|
|
53
|
+
return (
|
|
54
|
+
<Badge variant="outline" className="border-success/50 text-success">
|
|
55
|
+
Connected via {oauthLabel}
|
|
56
|
+
</Badge>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
35
60
|
if (apiKeySource === "unknown") {
|
|
36
61
|
return (
|
|
37
62
|
<Badge variant="outline" className="border-success/50 text-success">
|