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,118 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import { db } from "@/lib/db";
|
|
3
|
+
import { tasks, schedules, projects, settings, usageLedger } from "@/lib/db/schema";
|
|
4
|
+
import { eq } from "drizzle-orm";
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
6
|
+
import { NextRequest } from "next/server";
|
|
7
|
+
import { POST } from "../[id]/execute/route";
|
|
8
|
+
|
|
9
|
+
vi.mock("@/lib/agents/runtime", () => ({
|
|
10
|
+
executeTaskWithRuntime: vi.fn().mockResolvedValue(undefined),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
function req(url: string): NextRequest {
|
|
14
|
+
return new NextRequest(new URL(url, "http://localhost"));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function seedSchedule(): string {
|
|
18
|
+
const pid = randomUUID();
|
|
19
|
+
const sid = randomUUID();
|
|
20
|
+
const now = new Date();
|
|
21
|
+
db.insert(projects)
|
|
22
|
+
.values({ id: pid, name: "p", status: "active", createdAt: now, updatedAt: now })
|
|
23
|
+
.run();
|
|
24
|
+
db.insert(schedules)
|
|
25
|
+
.values({
|
|
26
|
+
id: sid,
|
|
27
|
+
projectId: pid,
|
|
28
|
+
name: "manual",
|
|
29
|
+
prompt: "test",
|
|
30
|
+
cronExpression: "0 0 * * *",
|
|
31
|
+
status: "active",
|
|
32
|
+
type: "scheduled",
|
|
33
|
+
firingCount: 0,
|
|
34
|
+
suppressionCount: 0,
|
|
35
|
+
heartbeatSpentToday: 0,
|
|
36
|
+
failureStreak: 0,
|
|
37
|
+
turnBudgetBreachStreak: 0,
|
|
38
|
+
createdAt: now,
|
|
39
|
+
updatedAt: now,
|
|
40
|
+
})
|
|
41
|
+
.run();
|
|
42
|
+
return sid;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("POST /api/schedules/:id/execute", () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
db.delete(usageLedger).run();
|
|
48
|
+
db.delete(tasks).run();
|
|
49
|
+
db.delete(schedules).run();
|
|
50
|
+
db.delete(projects).run();
|
|
51
|
+
db.delete(settings).where(eq(settings.key, "schedule.maxConcurrent")).run();
|
|
52
|
+
db.insert(settings)
|
|
53
|
+
.values({ key: "schedule.maxConcurrent", value: "1", updatedAt: new Date() })
|
|
54
|
+
.run();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("fires when capacity available, returns 200 with taskId", async () => {
|
|
58
|
+
const sid = seedSchedule();
|
|
59
|
+
const res = await POST(req(`/api/schedules/${sid}/execute`), {
|
|
60
|
+
params: Promise.resolve({ id: sid }),
|
|
61
|
+
});
|
|
62
|
+
expect(res.status).toBe(200);
|
|
63
|
+
const body = await res.json();
|
|
64
|
+
expect(body.taskId).toBeDefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("returns 429 when cap is full", async () => {
|
|
68
|
+
const sid1 = seedSchedule();
|
|
69
|
+
const sid2 = seedSchedule();
|
|
70
|
+
|
|
71
|
+
const res1 = await POST(req(`/api/schedules/${sid1}/execute`), {
|
|
72
|
+
params: Promise.resolve({ id: sid1 }),
|
|
73
|
+
});
|
|
74
|
+
expect(res1.status).toBe(200);
|
|
75
|
+
|
|
76
|
+
const res2 = await POST(req(`/api/schedules/${sid2}/execute`), {
|
|
77
|
+
params: Promise.resolve({ id: sid2 }),
|
|
78
|
+
});
|
|
79
|
+
expect(res2.status).toBe(429);
|
|
80
|
+
const body = await res2.json();
|
|
81
|
+
expect(body.error).toBe("capacity_full");
|
|
82
|
+
expect(body.slotEtaSec).toBeGreaterThanOrEqual(0);
|
|
83
|
+
|
|
84
|
+
const remaining = db.select().from(tasks).all();
|
|
85
|
+
expect(remaining.length).toBe(1); // only sid1's task remains; sid2's was cleaned up on refusal
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("bypasses the cap when ?force=true and writes audit-log entry", async () => {
|
|
89
|
+
const sid1 = seedSchedule();
|
|
90
|
+
const sid2 = seedSchedule();
|
|
91
|
+
|
|
92
|
+
await POST(req(`/api/schedules/${sid1}/execute`), {
|
|
93
|
+
params: Promise.resolve({ id: sid1 }),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const res2 = await POST(
|
|
97
|
+
req(`/api/schedules/${sid2}/execute?force=true`),
|
|
98
|
+
{ params: Promise.resolve({ id: sid2 }) },
|
|
99
|
+
);
|
|
100
|
+
expect(res2.status).toBe(200);
|
|
101
|
+
const body2 = await res2.json();
|
|
102
|
+
|
|
103
|
+
const ledger = db
|
|
104
|
+
.select()
|
|
105
|
+
.from(usageLedger)
|
|
106
|
+
.where(eq(usageLedger.activityType, "manual_force_bypass"))
|
|
107
|
+
.all();
|
|
108
|
+
expect(ledger.length).toBe(1);
|
|
109
|
+
expect(ledger[0].taskId).toBe(body2.taskId);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("returns 404 when the schedule does not exist", async () => {
|
|
113
|
+
const res = await POST(req("/api/schedules/nonexistent/execute"), {
|
|
114
|
+
params: Promise.resolve({ id: "nonexistent" }),
|
|
115
|
+
});
|
|
116
|
+
expect(res.status).toBe(404);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -6,9 +6,7 @@ import { parseInterval, computeNextFireTime } from "@/lib/schedules/interval-par
|
|
|
6
6
|
import { parseNaturalLanguage } from "@/lib/schedules/nlp-parser";
|
|
7
7
|
import { resolveAgentRuntime } from "@/lib/agents/runtime/catalog";
|
|
8
8
|
import { validateRuntimeProfileAssignment } from "@/lib/agents/profiles/assignment-validation";
|
|
9
|
-
import {
|
|
10
|
-
import { getActiveScheduleCount } from "@/lib/license/limit-queries";
|
|
11
|
-
import { createTierLimitNotification } from "@/lib/license/notifications";
|
|
9
|
+
import { checkCollision } from "@/lib/schedules/collision-check";
|
|
12
10
|
|
|
13
11
|
export async function GET() {
|
|
14
12
|
const result = await db
|
|
@@ -130,14 +128,6 @@ export async function POST(req: NextRequest) {
|
|
|
130
128
|
return NextResponse.json({ error: compatibilityError }, { status: 400 });
|
|
131
129
|
}
|
|
132
130
|
|
|
133
|
-
// Tier limit check — active schedule cap
|
|
134
|
-
const activeCount = getActiveScheduleCount();
|
|
135
|
-
const limitResult = checkLimit("activeSchedules", activeCount);
|
|
136
|
-
if (!limitResult.allowed) {
|
|
137
|
-
createTierLimitNotification("activeSchedules", activeCount, limitResult.limit).catch(() => {});
|
|
138
|
-
return NextResponse.json(buildLimitErrorBody("activeSchedules", limitResult), { status: 402 });
|
|
139
|
-
}
|
|
140
|
-
|
|
141
131
|
const id = crypto.randomUUID();
|
|
142
132
|
const now = new Date();
|
|
143
133
|
const nextFireAt = computeNextFireTime(cronExpression, now);
|
|
@@ -201,5 +191,6 @@ export async function POST(req: NextRequest) {
|
|
|
201
191
|
.from(schedules)
|
|
202
192
|
.where(eq(schedules.id, id));
|
|
203
193
|
|
|
204
|
-
|
|
194
|
+
const warnings = checkCollision(cronExpression, 0, projectId ?? null, null);
|
|
195
|
+
return NextResponse.json({ schedule: created, warnings }, { status: 201 });
|
|
205
196
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { getSetting, setSetting } from "@/lib/settings/helpers";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* GET/PUT `/api/settings/chat/pins` — per-user pinned entities for chat
|
|
7
|
+
* mention popover. Storage is a single JSON blob under the
|
|
8
|
+
* `chat.pinnedEntries` key in the `settings` key-value table.
|
|
9
|
+
*
|
|
10
|
+
* Design notes:
|
|
11
|
+
* - No server-side validation that `entityId` exists in its table.
|
|
12
|
+
* Pins are weakly referenced; popover filter just won't match if the
|
|
13
|
+
* entity has been deleted. Cheaper than maintaining referential integrity
|
|
14
|
+
* via cascading deletes, and stale pins can be removed by the user on next
|
|
15
|
+
* un-pin. Trade-off accepted.
|
|
16
|
+
* - PUT replaces the entire list (client is source of truth). Read-modify-
|
|
17
|
+
* write happens on the client to avoid concurrent-mutation issues, which
|
|
18
|
+
* for single-user local usage is a non-issue.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const SETTINGS_KEY = "chat.pinnedEntries";
|
|
22
|
+
|
|
23
|
+
// Entity types recognized by entities/search — mirrored here to constrain
|
|
24
|
+
// what can be pinned. Add new types as they become available.
|
|
25
|
+
const ENTITY_TYPES = [
|
|
26
|
+
"task",
|
|
27
|
+
"project",
|
|
28
|
+
"workflow",
|
|
29
|
+
"document",
|
|
30
|
+
"schedule",
|
|
31
|
+
"table",
|
|
32
|
+
"profile",
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
// We denormalize `label`, `description`, and `status` into the pin record
|
|
36
|
+
// so the Pinned group renders standalone, independent of whether the item
|
|
37
|
+
// appears in the current `entities/search` response window (top-20 per type).
|
|
38
|
+
// Trade-off: labels may go stale if the underlying entity is renamed.
|
|
39
|
+
// Acceptable for a UX affordance — selecting the pin still uses the canonical
|
|
40
|
+
// id, and the user can un-pin/re-pin to refresh. Mitigation is lazy refresh
|
|
41
|
+
// on next popover open (future enhancement).
|
|
42
|
+
const PinnedEntrySchema = z.object({
|
|
43
|
+
id: z.string().min(1),
|
|
44
|
+
type: z.enum(ENTITY_TYPES),
|
|
45
|
+
label: z.string().min(1),
|
|
46
|
+
description: z.string().optional(),
|
|
47
|
+
status: z.string().optional(),
|
|
48
|
+
pinnedAt: z.string(), // ISO 8601
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const PinsPayloadSchema = z.object({
|
|
52
|
+
pins: z.array(PinnedEntrySchema),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export type PinnedEntry = z.infer<typeof PinnedEntrySchema>;
|
|
56
|
+
|
|
57
|
+
export async function GET() {
|
|
58
|
+
const raw = await getSetting(SETTINGS_KEY);
|
|
59
|
+
if (!raw) return NextResponse.json({ pins: [] });
|
|
60
|
+
try {
|
|
61
|
+
const parsed = PinsPayloadSchema.parse(JSON.parse(raw));
|
|
62
|
+
return NextResponse.json(parsed);
|
|
63
|
+
} catch {
|
|
64
|
+
// Malformed stored value (manual edit, version mismatch) — recover by
|
|
65
|
+
// returning an empty list rather than erroring. The user can re-pin.
|
|
66
|
+
return NextResponse.json({ pins: [] });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function PUT(req: NextRequest) {
|
|
71
|
+
let body: unknown;
|
|
72
|
+
try {
|
|
73
|
+
body = await req.json();
|
|
74
|
+
} catch {
|
|
75
|
+
return NextResponse.json({ error: "invalid JSON body" }, { status: 400 });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const result = PinsPayloadSchema.safeParse(body);
|
|
79
|
+
if (!result.success) {
|
|
80
|
+
return NextResponse.json(
|
|
81
|
+
{ error: "invalid pins payload", issues: result.error.issues },
|
|
82
|
+
{ status: 400 }
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// De-dup by id — client may send the same pin twice on rapid clicks.
|
|
87
|
+
// Last write wins for pinnedAt.
|
|
88
|
+
const byId = new Map<string, PinnedEntry>();
|
|
89
|
+
for (const pin of result.data.pins) byId.set(pin.id, pin);
|
|
90
|
+
const deduped = Array.from(byId.values());
|
|
91
|
+
|
|
92
|
+
await setSetting(SETTINGS_KEY, JSON.stringify({ pins: deduped }));
|
|
93
|
+
return NextResponse.json({ pins: deduped });
|
|
94
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("@/lib/settings/helpers", () => {
|
|
4
|
+
const store = new Map<string, string>();
|
|
5
|
+
return {
|
|
6
|
+
getSetting: vi.fn(async (k: string) => store.get(k) ?? null),
|
|
7
|
+
setSetting: vi.fn(async (k: string, v: string) => {
|
|
8
|
+
store.set(k, v);
|
|
9
|
+
}),
|
|
10
|
+
__store: store,
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
import * as helpers from "@/lib/settings/helpers";
|
|
15
|
+
import { GET, PUT } from "../route";
|
|
16
|
+
import { NextRequest } from "next/server";
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
// Reset the in-memory store between tests
|
|
20
|
+
(helpers as unknown as { __store: Map<string, string> }).__store.clear();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("saved-searches route", () => {
|
|
24
|
+
it("GET with no stored value returns empty list", async () => {
|
|
25
|
+
const res = await GET();
|
|
26
|
+
const body = await res.json();
|
|
27
|
+
expect(body).toEqual({ searches: [] });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("PUT then GET round-trips a valid payload", async () => {
|
|
31
|
+
const payload = {
|
|
32
|
+
searches: [
|
|
33
|
+
{
|
|
34
|
+
id: "a",
|
|
35
|
+
surface: "task" as const,
|
|
36
|
+
label: "Blocked",
|
|
37
|
+
filterInput: "#status:blocked",
|
|
38
|
+
createdAt: "2026-04-14T00:00:00Z",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
const putRes = await PUT(
|
|
43
|
+
new NextRequest("http://x/api/settings/chat/saved-searches", {
|
|
44
|
+
method: "PUT",
|
|
45
|
+
body: JSON.stringify(payload),
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
expect(putRes.status).toBe(200);
|
|
49
|
+
|
|
50
|
+
const getBody = await (await GET()).json();
|
|
51
|
+
expect(getBody.searches).toHaveLength(1);
|
|
52
|
+
expect(getBody.searches[0].label).toBe("Blocked");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("PUT dedupes by id — last write wins", async () => {
|
|
56
|
+
const req = new NextRequest("http://x", {
|
|
57
|
+
method: "PUT",
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
searches: [
|
|
60
|
+
{
|
|
61
|
+
id: "a",
|
|
62
|
+
surface: "task",
|
|
63
|
+
label: "First",
|
|
64
|
+
filterInput: "#a:1",
|
|
65
|
+
createdAt: "2026-04-14T00:00:00Z",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "a",
|
|
69
|
+
surface: "task",
|
|
70
|
+
label: "Second (dup)",
|
|
71
|
+
filterInput: "#a:1",
|
|
72
|
+
createdAt: "2026-04-14T00:01:00Z",
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
const res = await PUT(req);
|
|
78
|
+
const body = await res.json();
|
|
79
|
+
expect(body.searches).toHaveLength(1);
|
|
80
|
+
expect(body.searches[0].label).toBe("Second (dup)");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("PUT rejects invalid surface with 400", async () => {
|
|
84
|
+
const req = new NextRequest("http://x", {
|
|
85
|
+
method: "PUT",
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
searches: [
|
|
88
|
+
{
|
|
89
|
+
id: "a",
|
|
90
|
+
surface: "bogus",
|
|
91
|
+
label: "x",
|
|
92
|
+
filterInput: "",
|
|
93
|
+
createdAt: "z",
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
const res = await PUT(req);
|
|
99
|
+
expect(res.status).toBe(400);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("PUT rejects malformed JSON with 400", async () => {
|
|
103
|
+
const req = new NextRequest("http://x", {
|
|
104
|
+
method: "PUT",
|
|
105
|
+
body: "not json",
|
|
106
|
+
});
|
|
107
|
+
const res = await PUT(req);
|
|
108
|
+
expect(res.status).toBe(400);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("GET recovers from malformed stored value", async () => {
|
|
112
|
+
(helpers as unknown as { __store: Map<string, string> }).__store.set(
|
|
113
|
+
"chat.savedSearches",
|
|
114
|
+
"not-json-at-all"
|
|
115
|
+
);
|
|
116
|
+
const body = await (await GET()).json();
|
|
117
|
+
expect(body).toEqual({ searches: [] });
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { getSetting, setSetting } from "@/lib/settings/helpers";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* GET/PUT `/api/settings/chat/saved-searches` — per-user saved filter
|
|
7
|
+
* combinations for chat popovers and the ⌘K palette. Storage mirrors the
|
|
8
|
+
* pins route: a single JSON blob under `chat.savedSearches` in the
|
|
9
|
+
* settings key-value table.
|
|
10
|
+
*
|
|
11
|
+
* Design notes:
|
|
12
|
+
* - Full-list replacement on PUT. Client is source of truth — avoids
|
|
13
|
+
* concurrent-mutation bookkeeping for a single-user local product.
|
|
14
|
+
* - PUT dedupes by `id` with last-write-wins on rapid double-saves.
|
|
15
|
+
* - Malformed stored value (manual edit, schema drift) degrades to `[]`
|
|
16
|
+
* rather than erroring — the user can re-save.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const SETTINGS_KEY = "chat.savedSearches";
|
|
20
|
+
|
|
21
|
+
// Surfaces map to popover tabs + list routes. Extend as new popover
|
|
22
|
+
// surfaces are added (table, schedule, etc.).
|
|
23
|
+
const SURFACES = [
|
|
24
|
+
"task",
|
|
25
|
+
"project",
|
|
26
|
+
"workflow",
|
|
27
|
+
"document",
|
|
28
|
+
"skill",
|
|
29
|
+
"profile",
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
const SavedSearchSchema = z.object({
|
|
33
|
+
id: z.string().min(1),
|
|
34
|
+
surface: z.enum(SURFACES),
|
|
35
|
+
label: z.string().min(1).max(120),
|
|
36
|
+
filterInput: z.string().max(500),
|
|
37
|
+
createdAt: z.string(), // ISO 8601
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const PayloadSchema = z.object({
|
|
41
|
+
searches: z.array(SavedSearchSchema),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export type SavedSearch = z.infer<typeof SavedSearchSchema>;
|
|
45
|
+
|
|
46
|
+
export async function GET() {
|
|
47
|
+
const raw = await getSetting(SETTINGS_KEY);
|
|
48
|
+
if (!raw) return NextResponse.json({ searches: [] });
|
|
49
|
+
try {
|
|
50
|
+
const parsed = PayloadSchema.parse(JSON.parse(raw));
|
|
51
|
+
return NextResponse.json(parsed);
|
|
52
|
+
} catch {
|
|
53
|
+
return NextResponse.json({ searches: [] });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function PUT(req: NextRequest) {
|
|
58
|
+
let body: unknown;
|
|
59
|
+
try {
|
|
60
|
+
body = await req.json();
|
|
61
|
+
} catch {
|
|
62
|
+
return NextResponse.json({ error: "invalid JSON body" }, { status: 400 });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const result = PayloadSchema.safeParse(body);
|
|
66
|
+
if (!result.success) {
|
|
67
|
+
return NextResponse.json(
|
|
68
|
+
{ error: "invalid searches payload", issues: result.error.issues },
|
|
69
|
+
{ status: 400 }
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const byId = new Map<string, SavedSearch>();
|
|
74
|
+
for (const s of result.data.searches) byId.set(s.id, s);
|
|
75
|
+
const deduped = Array.from(byId.values());
|
|
76
|
+
|
|
77
|
+
await setSetting(SETTINGS_KEY, JSON.stringify({ searches: deduped }));
|
|
78
|
+
return NextResponse.json({ searches: deduped });
|
|
79
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { getSetting, setSetting } from "@/lib/settings/helpers";
|
|
3
|
+
import { SETTINGS_KEYS } from "@/lib/constants/settings";
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const autoPromote = await getSetting(SETTINGS_KEYS.AUTO_PROMOTE_SKILLS);
|
|
7
|
+
return NextResponse.json({
|
|
8
|
+
autoPromoteSkills: autoPromote === "true",
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function POST(req: NextRequest) {
|
|
13
|
+
const body = await req.json();
|
|
14
|
+
|
|
15
|
+
if (body.autoPromoteSkills !== undefined) {
|
|
16
|
+
await setSetting(
|
|
17
|
+
SETTINGS_KEYS.AUTO_PROMOTE_SKILLS,
|
|
18
|
+
body.autoPromoteSkills ? "true" : "false"
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const autoPromote = await getSetting(SETTINGS_KEYS.AUTO_PROMOTE_SKILLS);
|
|
23
|
+
return NextResponse.json({
|
|
24
|
+
autoPromoteSkills: autoPromote === "true",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import {
|
|
3
|
+
cancelOpenAIChatGPTLogin,
|
|
4
|
+
getOpenAILoginState,
|
|
5
|
+
startOpenAIChatGPTLogin,
|
|
6
|
+
} from "@/lib/settings/openai-login-manager";
|
|
7
|
+
import { setOpenAIAuthSettings } from "@/lib/settings/openai-auth";
|
|
8
|
+
|
|
9
|
+
export async function GET() {
|
|
10
|
+
return NextResponse.json(getOpenAILoginState());
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function POST() {
|
|
14
|
+
await setOpenAIAuthSettings({ method: "oauth" });
|
|
15
|
+
const state = await startOpenAIChatGPTLogin();
|
|
16
|
+
return NextResponse.json(state);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function DELETE() {
|
|
20
|
+
const state = await cancelOpenAIChatGPTLogin();
|
|
21
|
+
return NextResponse.json(state);
|
|
22
|
+
}
|
|
@@ -3,11 +3,31 @@ import {
|
|
|
3
3
|
getOpenAIAuthSettings,
|
|
4
4
|
setOpenAIAuthSettings,
|
|
5
5
|
} from "@/lib/settings/openai-auth";
|
|
6
|
+
import { readStagentCodexAuthState } from "@/lib/agents/runtime/openai-codex-auth";
|
|
6
7
|
import { updateOpenAISettingsSchema } from "@/lib/validators/settings";
|
|
7
8
|
|
|
8
9
|
export async function GET() {
|
|
9
10
|
const settings = await getOpenAIAuthSettings();
|
|
10
|
-
|
|
11
|
+
if (settings.method !== "oauth") {
|
|
12
|
+
return NextResponse.json(settings);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const current = await readStagentCodexAuthState({ refreshToken: true });
|
|
17
|
+
return NextResponse.json({
|
|
18
|
+
...settings,
|
|
19
|
+
oauthConnected: current.connected,
|
|
20
|
+
account: current.account,
|
|
21
|
+
rateLimits: current.rateLimits,
|
|
22
|
+
});
|
|
23
|
+
} catch {
|
|
24
|
+
return NextResponse.json({
|
|
25
|
+
...settings,
|
|
26
|
+
oauthConnected: false,
|
|
27
|
+
account: null,
|
|
28
|
+
rateLimits: null,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
11
31
|
}
|
|
12
32
|
|
|
13
33
|
export async function POST(req: NextRequest) {
|
|
@@ -1,17 +1,39 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
+
import { readStagentCodexAuthState } from "@/lib/agents/runtime/openai-codex-auth";
|
|
2
3
|
import { getRuntimeSetupStates } from "@/lib/settings/runtime-setup";
|
|
3
4
|
import { getRoutingPreference } from "@/lib/settings/routing";
|
|
4
5
|
import { getAuthSettings } from "@/lib/settings/auth";
|
|
5
6
|
import { getOpenAIAuthSettings } from "@/lib/settings/openai-auth";
|
|
7
|
+
import { getOpenAILoginState } from "@/lib/settings/openai-login-manager";
|
|
6
8
|
|
|
7
9
|
export async function GET() {
|
|
8
|
-
const [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const [routingPreference, anthropicAuth, initialOpenaiAuth] = await Promise.all([
|
|
11
|
+
getRoutingPreference(),
|
|
12
|
+
getAuthSettings(),
|
|
13
|
+
getOpenAIAuthSettings(),
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
let openaiAuth = initialOpenaiAuth;
|
|
17
|
+
if (openaiAuth.method === "oauth") {
|
|
18
|
+
try {
|
|
19
|
+
const current = await readStagentCodexAuthState({ refreshToken: true });
|
|
20
|
+
openaiAuth = {
|
|
21
|
+
...openaiAuth,
|
|
22
|
+
oauthConnected: current.connected,
|
|
23
|
+
account: current.account,
|
|
24
|
+
rateLimits: current.rateLimits,
|
|
25
|
+
};
|
|
26
|
+
} catch {
|
|
27
|
+
openaiAuth = {
|
|
28
|
+
...openaiAuth,
|
|
29
|
+
oauthConnected: false,
|
|
30
|
+
account: null,
|
|
31
|
+
rateLimits: null,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const runtimeStates = await getRuntimeSetupStates();
|
|
15
37
|
|
|
16
38
|
const anthropicConfigured =
|
|
17
39
|
runtimeStates["claude-code"].configured ||
|
|
@@ -42,9 +64,14 @@ export async function GET() {
|
|
|
42
64
|
},
|
|
43
65
|
openai: {
|
|
44
66
|
configured: openaiConfigured,
|
|
67
|
+
authMethod: openaiAuth.method,
|
|
45
68
|
hasKey: openaiAuth.hasKey,
|
|
46
69
|
apiKeySource: openaiAuth.apiKeySource,
|
|
47
|
-
|
|
70
|
+
oauthConnected: openaiAuth.oauthConnected,
|
|
71
|
+
account: openaiAuth.account,
|
|
72
|
+
rateLimits: openaiAuth.rateLimits,
|
|
73
|
+
login: getOpenAILoginState(),
|
|
74
|
+
dualBilling: openaiAuth.oauthConnected && openaiAuth.hasKey,
|
|
48
75
|
runtimes: [
|
|
49
76
|
runtimeStates["openai-codex-app-server"],
|
|
50
77
|
runtimeStates["openai-direct"],
|