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,26 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { tick } from "@/lib/instance/upgrade-poller";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* POST /api/instance/upgrade/check
|
|
6
|
+
*
|
|
7
|
+
* Force-run the upgrade availability poller. Rate-limited to one call per
|
|
8
|
+
* ~5 minutes via the same lock file the scheduled poller uses. Returns the
|
|
9
|
+
* new UpgradeState on success, or a skipped reason if the lock was held or
|
|
10
|
+
* dev-mode was active.
|
|
11
|
+
*/
|
|
12
|
+
export async function POST() {
|
|
13
|
+
try {
|
|
14
|
+
const result = await tick();
|
|
15
|
+
if (result.updated) {
|
|
16
|
+
return NextResponse.json({ ok: true, state: result.updated });
|
|
17
|
+
}
|
|
18
|
+
return NextResponse.json(
|
|
19
|
+
{ ok: false, skipped: result.skipped, error: result.error },
|
|
20
|
+
{ status: 202 }
|
|
21
|
+
);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
24
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { db } from "@/lib/db";
|
|
4
|
+
import { tasks } from "@/lib/db/schema";
|
|
5
|
+
import {
|
|
6
|
+
getInstanceConfig,
|
|
7
|
+
getUpgradeState,
|
|
8
|
+
setUpgradeState,
|
|
9
|
+
} from "@/lib/instance/settings";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* POST /api/instance/upgrade
|
|
13
|
+
*
|
|
14
|
+
* Spawns an upgrade task with the `upgrade-assistant` agent profile. Returns
|
|
15
|
+
* 202 Accepted with the task id; the client then navigates to the upgrade
|
|
16
|
+
* session view to watch streaming progress and respond to conflict prompts.
|
|
17
|
+
*
|
|
18
|
+
* The task description includes the instance context (branch name, commits
|
|
19
|
+
* behind, data directory) as template variables that the profile's SKILL.md
|
|
20
|
+
* references. The claude-agent runtime interpolates them when building the
|
|
21
|
+
* system prompt.
|
|
22
|
+
*
|
|
23
|
+
* Fire-and-forget per TDR-001: the route returns immediately; task execution
|
|
24
|
+
* runs in the background through the existing execution-manager pipeline.
|
|
25
|
+
*/
|
|
26
|
+
export async function POST() {
|
|
27
|
+
try {
|
|
28
|
+
const config = getInstanceConfig();
|
|
29
|
+
if (!config) {
|
|
30
|
+
return NextResponse.json(
|
|
31
|
+
{ error: "Instance not yet initialized — run POST /api/instance/init first" },
|
|
32
|
+
{ status: 409 }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const upgrade = getUpgradeState();
|
|
37
|
+
if (!upgrade.upgradeAvailable) {
|
|
38
|
+
return NextResponse.json(
|
|
39
|
+
{ error: "No upgrade available", upgradeState: upgrade },
|
|
40
|
+
{ status: 409 }
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const branchName = config.branchName;
|
|
45
|
+
const commitsBehind = upgrade.commitsBehind;
|
|
46
|
+
const dataDir = process.env.STAGENT_DATA_DIR ?? "~/.stagent";
|
|
47
|
+
|
|
48
|
+
const description = [
|
|
49
|
+
`Upgrade instance branch \`${branchName}\` with ${commitsBehind} upstream commit(s) from origin/main.`,
|
|
50
|
+
"",
|
|
51
|
+
"Context for the upgrade-assistant profile:",
|
|
52
|
+
`- INSTANCE_BRANCH=${branchName}`,
|
|
53
|
+
`- COMMITS_BEHIND=${commitsBehind}`,
|
|
54
|
+
`- DATA_DIR=${dataDir}`,
|
|
55
|
+
"",
|
|
56
|
+
"Follow the standard merge flow defined in SKILL.md. Stop and ask the user on any merge conflict. Abort and roll back on any failure. Do not push any branch.",
|
|
57
|
+
].join("\n");
|
|
58
|
+
|
|
59
|
+
const id = randomUUID();
|
|
60
|
+
const now = new Date();
|
|
61
|
+
|
|
62
|
+
db.insert(tasks)
|
|
63
|
+
.values({
|
|
64
|
+
id,
|
|
65
|
+
title: `Upgrade ${branchName} — ${commitsBehind} upstream commit${commitsBehind === 1 ? "" : "s"}`,
|
|
66
|
+
description,
|
|
67
|
+
projectId: null,
|
|
68
|
+
priority: 1,
|
|
69
|
+
assignedAgent: null,
|
|
70
|
+
agentProfile: "upgrade-assistant",
|
|
71
|
+
sourceType: "manual",
|
|
72
|
+
status: "planned",
|
|
73
|
+
createdAt: now,
|
|
74
|
+
updatedAt: now,
|
|
75
|
+
})
|
|
76
|
+
.run();
|
|
77
|
+
|
|
78
|
+
// Record which task id owns this upgrade so the UI can deep-link to it,
|
|
79
|
+
// and optimistically clear the pending-count so the sidebar badge and
|
|
80
|
+
// settings card reflect the user's intent immediately. If the merge task
|
|
81
|
+
// fails or is cancelled, the next scheduled poll (or a manual "Check for
|
|
82
|
+
// upgrades") will restore the real count by re-running git rev-list.
|
|
83
|
+
await setUpgradeState({
|
|
84
|
+
...upgrade,
|
|
85
|
+
lastUpgradeTaskId: id,
|
|
86
|
+
commitsBehind: 0,
|
|
87
|
+
upgradeAvailable: false,
|
|
88
|
+
lastSuccessfulUpgradeAt: Math.floor(Date.now() / 1000),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return NextResponse.json({ taskId: id }, { status: 202 });
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
94
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getUpgradeState } from "@/lib/instance/settings";
|
|
3
|
+
import { isDevMode } from "@/lib/instance/detect";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* GET /api/instance/upgrade/status
|
|
7
|
+
*
|
|
8
|
+
* Returns the current UpgradeState for client components that need to poll
|
|
9
|
+
* (e.g. the upgrade modal pre-flight). Server Components should read directly
|
|
10
|
+
* from settings per TDR-004 rather than calling this route.
|
|
11
|
+
*
|
|
12
|
+
* When running on the canonical dev repo, returns a synthetic state with
|
|
13
|
+
* `devMode: true` and `upgradeAvailable: false` so the sidebar upgrade
|
|
14
|
+
* button never renders on main.
|
|
15
|
+
*/
|
|
16
|
+
export async function GET() {
|
|
17
|
+
try {
|
|
18
|
+
if (isDevMode()) {
|
|
19
|
+
return NextResponse.json({
|
|
20
|
+
devMode: true,
|
|
21
|
+
lastPolledAt: null,
|
|
22
|
+
upgradeAvailable: false,
|
|
23
|
+
commitsBehind: 0,
|
|
24
|
+
lastSuccessfulUpgradeAt: null,
|
|
25
|
+
pollFailureCount: 0,
|
|
26
|
+
lastPollError: null,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const state = getUpgradeState();
|
|
30
|
+
return NextResponse.json({ devMode: false, ...state });
|
|
31
|
+
} catch (err) {
|
|
32
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -3,9 +3,6 @@ import { db } from "@/lib/db";
|
|
|
3
3
|
import { agentMemory } from "@/lib/db/schema";
|
|
4
4
|
import { and, eq, desc } from "drizzle-orm";
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
6
|
-
import { checkLimit, buildLimitErrorBody } from "@/lib/license/limit-check";
|
|
7
|
-
import { getMemoryCount } from "@/lib/license/limit-queries";
|
|
8
|
-
import { createTierLimitNotification } from "@/lib/license/notifications";
|
|
9
6
|
|
|
10
7
|
/**
|
|
11
8
|
* GET /api/memory?profileId=xxx&category=fact&status=active
|
|
@@ -78,14 +75,6 @@ export async function POST(req: NextRequest) {
|
|
|
78
75
|
);
|
|
79
76
|
}
|
|
80
77
|
|
|
81
|
-
// Tier limit check — memory cap per profile
|
|
82
|
-
const currentCount = getMemoryCount(profileId);
|
|
83
|
-
const limitResult = checkLimit("agentMemories", currentCount);
|
|
84
|
-
if (!limitResult.allowed) {
|
|
85
|
-
createTierLimitNotification("agentMemories", currentCount, limitResult.limit).catch(() => {});
|
|
86
|
-
return NextResponse.json(buildLimitErrorBody("agentMemories", limitResult), { status: 402 });
|
|
87
|
-
}
|
|
88
|
-
|
|
89
78
|
const now = new Date();
|
|
90
79
|
const id = randomUUID();
|
|
91
80
|
// Convert 0-1 confidence to 0-1000, default 700
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { db } from "@/lib/db";
|
|
3
3
|
import { notifications } from "@/lib/db/schema";
|
|
4
|
-
import { eq, and, desc,
|
|
4
|
+
import { eq, and, desc, count } from "drizzle-orm";
|
|
5
|
+
|
|
6
|
+
import { buildDefaultNotificationVisibilityCondition } from "@/lib/notifications/visibility";
|
|
5
7
|
|
|
6
8
|
export async function GET(req: NextRequest) {
|
|
7
9
|
const url = new URL(req.url);
|
|
@@ -9,7 +11,7 @@ export async function GET(req: NextRequest) {
|
|
|
9
11
|
const type = url.searchParams.get("type");
|
|
10
12
|
const countOnly = url.searchParams.get("countOnly");
|
|
11
13
|
|
|
12
|
-
const conditions = [];
|
|
14
|
+
const conditions = [buildDefaultNotificationVisibilityCondition()];
|
|
13
15
|
if (unread === "true") conditions.push(eq(notifications.read, false));
|
|
14
16
|
if (type) conditions.push(eq(notifications.type, type as typeof notifications.type.enumValues[number]));
|
|
15
17
|
|
|
@@ -2,36 +2,11 @@ import { NextRequest, NextResponse } from "next/server";
|
|
|
2
2
|
import { db } from "@/lib/db";
|
|
3
3
|
import {
|
|
4
4
|
projects,
|
|
5
|
-
tasks,
|
|
6
|
-
workflows,
|
|
7
|
-
documents,
|
|
8
|
-
schedules,
|
|
9
|
-
agentLogs,
|
|
10
|
-
notifications,
|
|
11
|
-
learnedContext,
|
|
12
|
-
usageLedger,
|
|
13
|
-
environmentSyncOps,
|
|
14
|
-
environmentCheckpoints,
|
|
15
|
-
environmentArtifacts,
|
|
16
|
-
environmentScans,
|
|
17
|
-
chatMessages,
|
|
18
|
-
conversations,
|
|
19
5
|
projectDocumentDefaults,
|
|
20
|
-
userTables,
|
|
21
|
-
userTableColumns,
|
|
22
|
-
userTableRows,
|
|
23
|
-
userTableViews,
|
|
24
|
-
userTableImports,
|
|
25
|
-
userTableRelationships,
|
|
26
|
-
tableDocumentInputs,
|
|
27
|
-
taskTableInputs,
|
|
28
|
-
workflowTableInputs,
|
|
29
|
-
scheduleTableInputs,
|
|
30
|
-
userTableTriggers,
|
|
31
|
-
userTableRowHistory,
|
|
32
6
|
} from "@/lib/db/schema";
|
|
33
|
-
import { eq
|
|
7
|
+
import { eq } from "drizzle-orm";
|
|
34
8
|
import { updateProjectSchema } from "@/lib/validators/project";
|
|
9
|
+
import { deleteProjectCascade } from "@/lib/data/delete-project";
|
|
35
10
|
|
|
36
11
|
export async function GET(
|
|
37
12
|
_req: NextRequest,
|
|
@@ -109,137 +84,12 @@ export async function DELETE(
|
|
|
109
84
|
{ params }: { params: Promise<{ id: string }> }
|
|
110
85
|
) {
|
|
111
86
|
const { id } = await params;
|
|
112
|
-
const [existing] = await db
|
|
113
|
-
.select()
|
|
114
|
-
.from(projects)
|
|
115
|
-
.where(eq(projects.id, id));
|
|
116
|
-
|
|
117
|
-
if (!existing) {
|
|
118
|
-
return NextResponse.json({ error: "Not found" }, { status: 404 });
|
|
119
|
-
}
|
|
120
87
|
|
|
121
88
|
try {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// 1. Collect child IDs for nested FK chains
|
|
126
|
-
const taskIds = db
|
|
127
|
-
.select({ id: tasks.id })
|
|
128
|
-
.from(tasks)
|
|
129
|
-
.where(eq(tasks.projectId, id))
|
|
130
|
-
.all()
|
|
131
|
-
.map((r) => r.id);
|
|
132
|
-
|
|
133
|
-
const workflowIds = db
|
|
134
|
-
.select({ id: workflows.id })
|
|
135
|
-
.from(workflows)
|
|
136
|
-
.where(eq(workflows.projectId, id))
|
|
137
|
-
.all()
|
|
138
|
-
.map((r) => r.id);
|
|
139
|
-
|
|
140
|
-
const conversationIds = db
|
|
141
|
-
.select({ id: conversations.id })
|
|
142
|
-
.from(conversations)
|
|
143
|
-
.where(eq(conversations.projectId, id))
|
|
144
|
-
.all()
|
|
145
|
-
.map((r) => r.id);
|
|
146
|
-
|
|
147
|
-
const scanIds = db
|
|
148
|
-
.select({ id: environmentScans.id })
|
|
149
|
-
.from(environmentScans)
|
|
150
|
-
.where(eq(environmentScans.projectId, id))
|
|
151
|
-
.all()
|
|
152
|
-
.map((r) => r.id);
|
|
153
|
-
|
|
154
|
-
const checkpointIds = db
|
|
155
|
-
.select({ id: environmentCheckpoints.id })
|
|
156
|
-
.from(environmentCheckpoints)
|
|
157
|
-
.where(eq(environmentCheckpoints.projectId, id))
|
|
158
|
-
.all()
|
|
159
|
-
.map((r) => r.id);
|
|
160
|
-
|
|
161
|
-
// 2. Environment tables (deepest children first)
|
|
162
|
-
if (checkpointIds.length > 0) {
|
|
163
|
-
db.delete(environmentSyncOps)
|
|
164
|
-
.where(inArray(environmentSyncOps.checkpointId, checkpointIds))
|
|
165
|
-
.run();
|
|
166
|
-
db.delete(environmentCheckpoints)
|
|
167
|
-
.where(inArray(environmentCheckpoints.id, checkpointIds))
|
|
168
|
-
.run();
|
|
169
|
-
}
|
|
170
|
-
if (scanIds.length > 0) {
|
|
171
|
-
db.delete(environmentArtifacts)
|
|
172
|
-
.where(inArray(environmentArtifacts.scanId, scanIds))
|
|
173
|
-
.run();
|
|
174
|
-
db.delete(environmentScans)
|
|
175
|
-
.where(inArray(environmentScans.id, scanIds))
|
|
176
|
-
.run();
|
|
89
|
+
const deleted = deleteProjectCascade(id);
|
|
90
|
+
if (!deleted) {
|
|
91
|
+
return NextResponse.json({ error: "Not found" }, { status: 404 });
|
|
177
92
|
}
|
|
178
|
-
|
|
179
|
-
// 3. Chat tables (messages before conversations)
|
|
180
|
-
if (conversationIds.length > 0) {
|
|
181
|
-
db.delete(chatMessages)
|
|
182
|
-
.where(inArray(chatMessages.conversationId, conversationIds))
|
|
183
|
-
.run();
|
|
184
|
-
db.delete(conversations)
|
|
185
|
-
.where(inArray(conversations.id, conversationIds))
|
|
186
|
-
.run();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// 4. Usage ledger (references projectId, workflowId, taskId)
|
|
190
|
-
db.delete(usageLedger).where(eq(usageLedger.projectId, id)).run();
|
|
191
|
-
|
|
192
|
-
// 5. Task children (logs, notifications, documents, learned context)
|
|
193
|
-
if (taskIds.length > 0) {
|
|
194
|
-
db.delete(agentLogs).where(inArray(agentLogs.taskId, taskIds)).run();
|
|
195
|
-
db.delete(notifications)
|
|
196
|
-
.where(inArray(notifications.taskId, taskIds))
|
|
197
|
-
.run();
|
|
198
|
-
db.delete(documents).where(inArray(documents.taskId, taskIds)).run();
|
|
199
|
-
db.delete(learnedContext)
|
|
200
|
-
.where(inArray(learnedContext.sourceTaskId, taskIds))
|
|
201
|
-
.run();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// 6. Project document defaults (junction table)
|
|
205
|
-
db.delete(projectDocumentDefaults).where(eq(projectDocumentDefaults.projectId, id)).run();
|
|
206
|
-
|
|
207
|
-
// 6b. User-defined tables — cascade-delete children before parent
|
|
208
|
-
const tableIds = db
|
|
209
|
-
.select({ id: userTables.id })
|
|
210
|
-
.from(userTables)
|
|
211
|
-
.where(eq(userTables.projectId, id))
|
|
212
|
-
.all()
|
|
213
|
-
.map((r) => r.id);
|
|
214
|
-
|
|
215
|
-
if (tableIds.length > 0) {
|
|
216
|
-
// Junction tables first
|
|
217
|
-
db.delete(tableDocumentInputs).where(inArray(tableDocumentInputs.tableId, tableIds)).run();
|
|
218
|
-
db.delete(taskTableInputs).where(inArray(taskTableInputs.tableId, tableIds)).run();
|
|
219
|
-
db.delete(workflowTableInputs).where(inArray(workflowTableInputs.tableId, tableIds)).run();
|
|
220
|
-
db.delete(scheduleTableInputs).where(inArray(scheduleTableInputs.tableId, tableIds)).run();
|
|
221
|
-
// Children
|
|
222
|
-
db.delete(userTableRowHistory).where(inArray(userTableRowHistory.tableId, tableIds)).run();
|
|
223
|
-
db.delete(userTableTriggers).where(inArray(userTableTriggers.tableId, tableIds)).run();
|
|
224
|
-
db.delete(userTableImports).where(inArray(userTableImports.tableId, tableIds)).run();
|
|
225
|
-
db.delete(userTableViews).where(inArray(userTableViews.tableId, tableIds)).run();
|
|
226
|
-
db.delete(userTableRelationships).where(inArray(userTableRelationships.fromTableId, tableIds)).run();
|
|
227
|
-
db.delete(userTableRows).where(inArray(userTableRows.tableId, tableIds)).run();
|
|
228
|
-
db.delete(userTableColumns).where(inArray(userTableColumns.tableId, tableIds)).run();
|
|
229
|
-
db.delete(userTables).where(inArray(userTables.id, tableIds)).run();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// 7. Direct project children
|
|
233
|
-
db.delete(documents).where(eq(documents.projectId, id)).run();
|
|
234
|
-
db.delete(tasks).where(eq(tasks.projectId, id)).run();
|
|
235
|
-
if (workflowIds.length > 0) {
|
|
236
|
-
db.delete(workflows).where(inArray(workflows.id, workflowIds)).run();
|
|
237
|
-
}
|
|
238
|
-
db.delete(schedules).where(eq(schedules.projectId, id)).run();
|
|
239
|
-
|
|
240
|
-
// 7. Finally delete the project
|
|
241
|
-
db.delete(projects).where(eq(projects.id, id)).run();
|
|
242
|
-
|
|
243
93
|
return NextResponse.json({ success: true });
|
|
244
94
|
} catch (err) {
|
|
245
95
|
console.error("Project delete failed:", err);
|
|
@@ -6,14 +6,14 @@ import * as schema from "@/lib/db/schema";
|
|
|
6
6
|
/**
|
|
7
7
|
* Safety-net regression tests for project cascade deletion.
|
|
8
8
|
*
|
|
9
|
-
* These verify that the
|
|
10
|
-
* properly handles all FK relationships
|
|
11
|
-
* This prevents
|
|
12
|
-
*
|
|
9
|
+
* These verify that the shared deleteProjectCascade function in
|
|
10
|
+
* src/lib/data/delete-project.ts properly handles all FK relationships
|
|
11
|
+
* before deleting a project. This prevents "Failed to delete project"
|
|
12
|
+
* FK constraint errors when related records exist.
|
|
13
13
|
*/
|
|
14
14
|
describe("project DELETE cascade coverage", () => {
|
|
15
15
|
const deleteRouteSource = readFileSync(
|
|
16
|
-
join(__dirname, "..", "
|
|
16
|
+
join(__dirname, "..", "..", "..", "..", "lib", "data", "delete-project.ts"),
|
|
17
17
|
"utf-8"
|
|
18
18
|
);
|
|
19
19
|
|
|
@@ -115,7 +115,7 @@ describe("project DELETE cascade coverage", () => {
|
|
|
115
115
|
// Find the LAST occurrence of db.delete(child) and FIRST occurrence of db.delete(parent)
|
|
116
116
|
// within the DELETE function (not the import section)
|
|
117
117
|
const deleteSection = deleteRouteSource.slice(
|
|
118
|
-
deleteRouteSource.indexOf("export
|
|
118
|
+
deleteRouteSource.indexOf("export function deleteProjectCascade")
|
|
119
119
|
);
|
|
120
120
|
const childPos = deleteSection.lastIndexOf(`db.delete(${child})`);
|
|
121
121
|
const parentPos = deleteSection.indexOf(`db.delete(${parent})`);
|
|
@@ -129,21 +129,12 @@ describe("project DELETE cascade coverage", () => {
|
|
|
129
129
|
).toEqual([]);
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
it("
|
|
132
|
+
it("checks project existence before deleting", () => {
|
|
133
133
|
const deleteSection = deleteRouteSource.slice(
|
|
134
|
-
deleteRouteSource.indexOf("export
|
|
134
|
+
deleteRouteSource.indexOf("export function deleteProjectCascade")
|
|
135
135
|
);
|
|
136
|
-
|
|
137
|
-
expect(deleteSection).toContain("
|
|
138
|
-
expect(deleteSection).toContain("status: 500");
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("verifies project exists before attempting delete", () => {
|
|
142
|
-
const deleteSection = deleteRouteSource.slice(
|
|
143
|
-
deleteRouteSource.indexOf("export async function DELETE")
|
|
144
|
-
);
|
|
145
|
-
expect(deleteSection).toContain("Not found");
|
|
146
|
-
expect(deleteSection).toContain("status: 404");
|
|
136
|
+
// The shared function checks if the project exists and returns false if not
|
|
137
|
+
expect(deleteSection).toContain("if (!existing) return false");
|
|
147
138
|
});
|
|
148
139
|
|
|
149
140
|
/**
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { db } from "@/lib/db";
|
|
3
|
+
import { schedules, tasks, usageLedger } from "@/lib/db/schema";
|
|
4
|
+
import { eq } from "drizzle-orm";
|
|
5
|
+
import { claimSlot, countRunningScheduledSlots } from "@/lib/schedules/slot-claim";
|
|
6
|
+
import {
|
|
7
|
+
getScheduleMaxConcurrent,
|
|
8
|
+
getScheduleMaxRunDurationSec,
|
|
9
|
+
} from "@/lib/schedules/config";
|
|
10
|
+
import { randomUUID } from "crypto";
|
|
11
|
+
import { startTaskExecution } from "@/lib/agents/task-dispatch";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Manually fire a schedule. Honors the global concurrency cap by default.
|
|
15
|
+
* Use `?force=true` to bypass the cap (logged to usage_ledger as
|
|
16
|
+
* "manual_force_bypass" for audit).
|
|
17
|
+
*
|
|
18
|
+
* Security note: force bypass is audit-logged synchronously before task
|
|
19
|
+
* execution begins, so every bypass leaves a permanent record regardless of
|
|
20
|
+
* task outcome.
|
|
21
|
+
*/
|
|
22
|
+
export async function POST(
|
|
23
|
+
req: NextRequest,
|
|
24
|
+
{ params }: { params: Promise<{ id: string }> },
|
|
25
|
+
) {
|
|
26
|
+
const { id: scheduleId } = await params;
|
|
27
|
+
const force = req.nextUrl.searchParams.get("force") === "true";
|
|
28
|
+
|
|
29
|
+
const [schedule] = db
|
|
30
|
+
.select()
|
|
31
|
+
.from(schedules)
|
|
32
|
+
.where(eq(schedules.id, scheduleId))
|
|
33
|
+
.all();
|
|
34
|
+
if (!schedule) {
|
|
35
|
+
return NextResponse.json({ error: "schedule_not_found" }, { status: 404 });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const taskId = randomUUID();
|
|
39
|
+
const firingNumber = schedule.firingCount + 1;
|
|
40
|
+
const now = new Date();
|
|
41
|
+
|
|
42
|
+
db.insert(tasks)
|
|
43
|
+
.values({
|
|
44
|
+
id: taskId,
|
|
45
|
+
projectId: schedule.projectId,
|
|
46
|
+
workflowId: null,
|
|
47
|
+
scheduleId: schedule.id,
|
|
48
|
+
title: `${schedule.name} — manual firing #${firingNumber}`,
|
|
49
|
+
description: schedule.prompt,
|
|
50
|
+
status: "queued",
|
|
51
|
+
assignedAgent: schedule.assignedAgent,
|
|
52
|
+
agentProfile: schedule.agentProfile,
|
|
53
|
+
priority: 2,
|
|
54
|
+
sourceType: "scheduled",
|
|
55
|
+
maxTurns: schedule.maxTurns,
|
|
56
|
+
createdAt: now,
|
|
57
|
+
updatedAt: now,
|
|
58
|
+
})
|
|
59
|
+
.run();
|
|
60
|
+
|
|
61
|
+
const cap = getScheduleMaxConcurrent();
|
|
62
|
+
const leaseSec = schedule.maxRunDurationSec ?? getScheduleMaxRunDurationSec();
|
|
63
|
+
|
|
64
|
+
// When force=true, pass an effectively infinite cap so the subquery COUNT
|
|
65
|
+
// can never exceed it. This lets `claimSlot` atomically transition the task
|
|
66
|
+
// to "running" even when the real cap is full.
|
|
67
|
+
const effectiveCap = force ? Number.MAX_SAFE_INTEGER : cap;
|
|
68
|
+
const { claimed } = claimSlot(taskId, effectiveCap, leaseSec);
|
|
69
|
+
|
|
70
|
+
if (!claimed) {
|
|
71
|
+
db.delete(tasks).where(eq(tasks.id, taskId)).run();
|
|
72
|
+
const slotEtaSec = 60;
|
|
73
|
+
return NextResponse.json(
|
|
74
|
+
{
|
|
75
|
+
error: "capacity_full",
|
|
76
|
+
message: `Swarm at capacity (${countRunningScheduledSlots()}/${cap}). Retry in ~${slotEtaSec}s or add ?force=true to bypass.`,
|
|
77
|
+
slotEtaSec,
|
|
78
|
+
},
|
|
79
|
+
{ status: 429 },
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Audit log written synchronously before task execution so that a force
|
|
84
|
+
// bypass is always recorded even if the task itself fails immediately.
|
|
85
|
+
if (force) {
|
|
86
|
+
const nowTs = new Date();
|
|
87
|
+
db.insert(usageLedger)
|
|
88
|
+
.values({
|
|
89
|
+
id: randomUUID(),
|
|
90
|
+
taskId,
|
|
91
|
+
scheduleId: schedule.id,
|
|
92
|
+
projectId: schedule.projectId,
|
|
93
|
+
activityType: "manual_force_bypass",
|
|
94
|
+
runtimeId: "manual",
|
|
95
|
+
providerId: "manual",
|
|
96
|
+
status: "completed",
|
|
97
|
+
costMicros: 0,
|
|
98
|
+
startedAt: nowTs,
|
|
99
|
+
finishedAt: nowTs,
|
|
100
|
+
})
|
|
101
|
+
.run();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Fire-and-forget: the route returns immediately with taskId; execution runs
|
|
105
|
+
// in the background. Errors are logged but do not affect the 200 response.
|
|
106
|
+
startTaskExecution(taskId).catch((err) => {
|
|
107
|
+
console.error(`[api/schedules/execute] task ${taskId} failed:`, err);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return NextResponse.json({ taskId, forced: force });
|
|
111
|
+
}
|
|
@@ -4,6 +4,7 @@ import { schedules, tasks } from "@/lib/db/schema";
|
|
|
4
4
|
import { eq, like } from "drizzle-orm";
|
|
5
5
|
import { parseInterval, computeNextFireTime } from "@/lib/schedules/interval-parser";
|
|
6
6
|
import { parseNaturalLanguage } from "@/lib/schedules/nlp-parser";
|
|
7
|
+
import { checkCollision } from "@/lib/schedules/collision-check";
|
|
7
8
|
import { resolveAgentRuntime } from "@/lib/agents/runtime/catalog";
|
|
8
9
|
import { validateRuntimeProfileAssignment } from "@/lib/agents/profiles/assignment-validation";
|
|
9
10
|
|
|
@@ -199,7 +200,14 @@ export async function PATCH(
|
|
|
199
200
|
.from(schedules)
|
|
200
201
|
.where(eq(schedules.id, id));
|
|
201
202
|
|
|
202
|
-
|
|
203
|
+
const effectiveCron = (updates.cronExpression as string | undefined) ?? schedule.cronExpression;
|
|
204
|
+
const warnings = checkCollision(
|
|
205
|
+
effectiveCron,
|
|
206
|
+
schedule.avgTurnsPerFiring ?? 0,
|
|
207
|
+
schedule.projectId ?? null,
|
|
208
|
+
schedule.id,
|
|
209
|
+
);
|
|
210
|
+
return NextResponse.json({ schedule: updated, warnings });
|
|
203
211
|
}
|
|
204
212
|
|
|
205
213
|
export async function DELETE(
|