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,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `useSavedSearches` — client-side store for saved filter combinations
|
|
5
|
+
* surfaced in the chat mention popover and `⌘K` palette.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors `use-pinned-entries.ts`: fetches once on mount, keeps an
|
|
8
|
+
* in-memory list, and writes back via PUT on every mutation (full-list
|
|
9
|
+
* replacement — see `src/app/api/settings/chat/saved-searches/route.ts`
|
|
10
|
+
* for design rationale).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useCallback, useEffect, useState } from "react";
|
|
14
|
+
|
|
15
|
+
export type SavedSearchSurface =
|
|
16
|
+
| "task"
|
|
17
|
+
| "project"
|
|
18
|
+
| "workflow"
|
|
19
|
+
| "document"
|
|
20
|
+
| "skill"
|
|
21
|
+
| "profile";
|
|
22
|
+
|
|
23
|
+
export interface SavedSearch {
|
|
24
|
+
id: string;
|
|
25
|
+
surface: SavedSearchSurface;
|
|
26
|
+
label: string;
|
|
27
|
+
filterInput: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface UseSavedSearchesReturn {
|
|
32
|
+
searches: SavedSearch[];
|
|
33
|
+
loading: boolean;
|
|
34
|
+
save: (entry: Omit<SavedSearch, "id" | "createdAt">) => SavedSearch;
|
|
35
|
+
remove: (id: string) => void;
|
|
36
|
+
forSurface: (surface: SavedSearchSurface) => SavedSearch[];
|
|
37
|
+
/**
|
|
38
|
+
* Re-fetch from the server. Each `useSavedSearches()` consumer holds
|
|
39
|
+
* its own state — the chat popover and the ⌘K palette do not share a
|
|
40
|
+
* cache. Components that need to see edits made elsewhere (e.g. the
|
|
41
|
+
* palette opening after a save in the popover) call `refetch()` at
|
|
42
|
+
* the right moment to revalidate.
|
|
43
|
+
*
|
|
44
|
+
* See features/saved-search-polish-v1.md for the bug history.
|
|
45
|
+
*/
|
|
46
|
+
refetch: () => Promise<void>;
|
|
47
|
+
rename: (id: string, label: string) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function useSavedSearches(): UseSavedSearchesReturn {
|
|
51
|
+
const [searches, setSearches] = useState<SavedSearch[]>([]);
|
|
52
|
+
const [loading, setLoading] = useState(true);
|
|
53
|
+
|
|
54
|
+
// Single fetch helper used by both the mount effect and `refetch`.
|
|
55
|
+
// Returns void so consumers can `await` revalidation if they want
|
|
56
|
+
// to wait for fresh data before continuing.
|
|
57
|
+
const fetchSearches = useCallback(async (): Promise<void> => {
|
|
58
|
+
try {
|
|
59
|
+
const r = await fetch("/api/settings/chat/saved-searches");
|
|
60
|
+
const data: { searches?: SavedSearch[] } = r.ok
|
|
61
|
+
? await r.json()
|
|
62
|
+
: { searches: [] };
|
|
63
|
+
setSearches(data.searches ?? []);
|
|
64
|
+
} catch {
|
|
65
|
+
setSearches([]);
|
|
66
|
+
} finally {
|
|
67
|
+
setLoading(false);
|
|
68
|
+
}
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
void fetchSearches();
|
|
73
|
+
}, [fetchSearches]);
|
|
74
|
+
|
|
75
|
+
const persist = useCallback(async (next: SavedSearch[]) => {
|
|
76
|
+
try {
|
|
77
|
+
await fetch("/api/settings/chat/saved-searches", {
|
|
78
|
+
method: "PUT",
|
|
79
|
+
headers: { "Content-Type": "application/json" },
|
|
80
|
+
body: JSON.stringify({ searches: next }),
|
|
81
|
+
});
|
|
82
|
+
} catch {
|
|
83
|
+
// Optimistic update already applied; server-sync failure silently
|
|
84
|
+
// swallowed. Matches the pins-hook contract.
|
|
85
|
+
}
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
const save = useCallback(
|
|
89
|
+
(entry: Omit<SavedSearch, "id" | "createdAt">): SavedSearch => {
|
|
90
|
+
const full: SavedSearch = {
|
|
91
|
+
...entry,
|
|
92
|
+
id:
|
|
93
|
+
typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
94
|
+
? crypto.randomUUID()
|
|
95
|
+
: `ss-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
96
|
+
createdAt: new Date().toISOString(),
|
|
97
|
+
};
|
|
98
|
+
setSearches((prev) => {
|
|
99
|
+
const next = [...prev, full];
|
|
100
|
+
void persist(next);
|
|
101
|
+
return next;
|
|
102
|
+
});
|
|
103
|
+
return full;
|
|
104
|
+
},
|
|
105
|
+
[persist]
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const remove = useCallback(
|
|
109
|
+
(id: string) => {
|
|
110
|
+
setSearches((prev) => {
|
|
111
|
+
const next = prev.filter((s) => s.id !== id);
|
|
112
|
+
void persist(next);
|
|
113
|
+
return next;
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
[persist]
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const forSurface = useCallback(
|
|
120
|
+
(surface: SavedSearchSurface) =>
|
|
121
|
+
searches.filter((s) => s.surface === surface),
|
|
122
|
+
[searches]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const refetch = useCallback(() => fetchSearches(), [fetchSearches]);
|
|
126
|
+
|
|
127
|
+
const rename = useCallback(
|
|
128
|
+
(id: string, label: string) => {
|
|
129
|
+
setSearches((prev) => {
|
|
130
|
+
const idx = prev.findIndex((s) => s.id === id);
|
|
131
|
+
if (idx === -1) return prev;
|
|
132
|
+
const next = prev.slice();
|
|
133
|
+
next[idx] = { ...next[idx], label };
|
|
134
|
+
void persist(next);
|
|
135
|
+
return next;
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
[persist]
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return { searches, loading, save, remove, forSurface, refetch, rename };
|
|
142
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export async function registerNodeInstrumentation() {
|
|
2
|
+
try {
|
|
3
|
+
// Instance bootstrap — creates local branch, handles dev-mode gates, consent flow.
|
|
4
|
+
// Runs BEFORE other startup so instance config is available downstream.
|
|
5
|
+
// Safe in the canonical stagent dev repo thanks to STAGENT_DEV_MODE=true
|
|
6
|
+
// in .env.local plus the .git/stagent-dev-mode sentinel file.
|
|
7
|
+
const { ensureInstance } = await import("@/lib/instance/bootstrap");
|
|
8
|
+
const instanceResult = await ensureInstance();
|
|
9
|
+
if (instanceResult.skipped) {
|
|
10
|
+
console.log(`[instance] bootstrap skipped: ${instanceResult.skipped}`);
|
|
11
|
+
} else {
|
|
12
|
+
for (const step of instanceResult.steps) {
|
|
13
|
+
if (step.status === "failed") {
|
|
14
|
+
console.error(`[instance] ${step.step} failed: ${step.reason}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Run pending Drizzle migrations (DROP TABLE, CREATE INDEX, etc.)
|
|
20
|
+
// that can't be handled by bootstrap's IF NOT EXISTS pattern.
|
|
21
|
+
// Runs here (not in db/index.ts) to avoid SQLITE_BUSY during next build.
|
|
22
|
+
await runPendingMigrations();
|
|
23
|
+
|
|
24
|
+
// Instance upgrade poller — hourly `git fetch` to detect upstream commits.
|
|
25
|
+
// Skipped in dev mode; lightweight; uses advisory lock to prevent overlap.
|
|
26
|
+
const { startUpgradePoller } = await import("@/lib/instance/upgrade-poller");
|
|
27
|
+
startUpgradePoller();
|
|
28
|
+
|
|
29
|
+
const { startScheduler } = await import("@/lib/schedules/scheduler");
|
|
30
|
+
startScheduler();
|
|
31
|
+
|
|
32
|
+
const { startChannelPoller } = await import("@/lib/channels/poller");
|
|
33
|
+
startChannelPoller();
|
|
34
|
+
|
|
35
|
+
const { startAutoBackup } = await import("@/lib/snapshots/auto-backup");
|
|
36
|
+
startAutoBackup();
|
|
37
|
+
|
|
38
|
+
// History retention cleanup — prunes old agent_logs and usage_ledger
|
|
39
|
+
startHistoryCleanup();
|
|
40
|
+
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.error("Instrumentation startup failed:", err);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function startHistoryCleanup() {
|
|
47
|
+
const CLEANUP_INTERVAL = 24 * 60 * 60 * 1000;
|
|
48
|
+
const RETENTION_DAYS = 365;
|
|
49
|
+
|
|
50
|
+
async function cleanup() {
|
|
51
|
+
const { db } = await import("@/lib/db");
|
|
52
|
+
const { agentLogs, usageLedger } = await import("@/lib/db/schema");
|
|
53
|
+
const { lt } = await import("drizzle-orm");
|
|
54
|
+
|
|
55
|
+
const cutoff = new Date(Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1000);
|
|
56
|
+
db.delete(agentLogs).where(lt(agentLogs.timestamp, cutoff)).run();
|
|
57
|
+
db.delete(usageLedger).where(lt(usageLedger.startedAt, cutoff)).run();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
cleanup().catch(() => {});
|
|
61
|
+
setInterval(() => cleanup().catch(() => {}), CLEANUP_INTERVAL);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function runPendingMigrations() {
|
|
65
|
+
const { join } = await import("path");
|
|
66
|
+
const { existsSync } = await import("fs");
|
|
67
|
+
const { getAppRoot } = await import("@/lib/utils/app-root");
|
|
68
|
+
|
|
69
|
+
const appRoot = getAppRoot(import.meta.dirname, 1);
|
|
70
|
+
const migrationsDir = join(appRoot, "src", "lib", "db", "migrations");
|
|
71
|
+
if (!existsSync(migrationsDir)) return; // npx distribution — no migration files
|
|
72
|
+
|
|
73
|
+
const { sqlite } = await import("@/lib/db");
|
|
74
|
+
const { drizzle } = await import("drizzle-orm/better-sqlite3");
|
|
75
|
+
const { migrate } = await import("drizzle-orm/better-sqlite3/migrator");
|
|
76
|
+
const {
|
|
77
|
+
hasLegacyStagentTables,
|
|
78
|
+
hasMigrationHistory,
|
|
79
|
+
markAllMigrationsApplied,
|
|
80
|
+
bootstrapStagentDatabase,
|
|
81
|
+
} = await import("@/lib/db/bootstrap");
|
|
82
|
+
|
|
83
|
+
const needsLegacyRecovery =
|
|
84
|
+
hasLegacyStagentTables(sqlite) && !hasMigrationHistory(sqlite);
|
|
85
|
+
|
|
86
|
+
if (needsLegacyRecovery) {
|
|
87
|
+
bootstrapStagentDatabase(sqlite);
|
|
88
|
+
markAllMigrationsApplied(sqlite, migrationsDir);
|
|
89
|
+
console.log("[db] Recovered legacy database — all migrations stamped.");
|
|
90
|
+
} else {
|
|
91
|
+
const db = drizzle(sqlite);
|
|
92
|
+
migrate(db, { migrationsFolder: migrationsDir });
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/instrumentation.ts
CHANGED
|
@@ -1,51 +1,7 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
try {
|
|
5
|
-
// License manager — initialize from DB (creates default row if needed)
|
|
6
|
-
const { licenseManager } = await import("@/lib/license/manager");
|
|
7
|
-
licenseManager.initialize();
|
|
8
|
-
licenseManager.startValidationTimer();
|
|
9
|
-
|
|
10
|
-
const { startScheduler } = await import("@/lib/schedules/scheduler");
|
|
11
|
-
startScheduler();
|
|
12
|
-
|
|
13
|
-
const { startChannelPoller } = await import("@/lib/channels/poller");
|
|
14
|
-
startChannelPoller();
|
|
15
|
-
|
|
16
|
-
const { startAutoBackup } = await import("@/lib/snapshots/auto-backup");
|
|
17
|
-
startAutoBackup();
|
|
18
|
-
|
|
19
|
-
// History retention cleanup — prunes old agent_logs and usage_ledger
|
|
20
|
-
// based on tier retention limit (Community: 30 days)
|
|
21
|
-
startHistoryCleanup(licenseManager);
|
|
22
|
-
|
|
23
|
-
// Telemetry batch flush (opt-in, every 5 minutes)
|
|
24
|
-
const { startTelemetryFlush } = await import("@/lib/telemetry/queue");
|
|
25
|
-
startTelemetryFlush();
|
|
26
|
-
} catch (err) {
|
|
27
|
-
console.error("Instrumentation startup failed:", err);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function startHistoryCleanup(licenseManager: { getLimit: (r: "historyRetentionDays") => number }) {
|
|
33
|
-
const CLEANUP_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
|
|
34
|
-
|
|
35
|
-
async function cleanup() {
|
|
36
|
-
const retentionDays = licenseManager.getLimit("historyRetentionDays");
|
|
37
|
-
if (!Number.isFinite(retentionDays)) return; // Unlimited retention
|
|
38
|
-
|
|
39
|
-
const { db } = await import("@/lib/db");
|
|
40
|
-
const { agentLogs, usageLedger } = await import("@/lib/db/schema");
|
|
41
|
-
const { lt } = await import("drizzle-orm");
|
|
42
|
-
|
|
43
|
-
const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);
|
|
44
|
-
db.delete(agentLogs).where(lt(agentLogs.timestamp, cutoff)).run();
|
|
45
|
-
db.delete(usageLedger).where(lt(usageLedger.startedAt, cutoff)).run();
|
|
1
|
+
export function register() {
|
|
2
|
+
if (process.env.NEXT_RUNTIME !== "nodejs") {
|
|
3
|
+
return;
|
|
46
4
|
}
|
|
47
5
|
|
|
48
|
-
|
|
49
|
-
cleanup().catch(() => {});
|
|
50
|
-
setInterval(() => cleanup().catch(() => {}), CLEANUP_INTERVAL);
|
|
6
|
+
return require("./instrumentation-node").registerNodeInstrumentation();
|
|
51
7
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
describe("claude-agent.ts SDK options parity with chat engine", () => {
|
|
6
|
+
const agentSource = fs.readFileSync(
|
|
7
|
+
path.resolve(__dirname, "../claude-agent.ts"),
|
|
8
|
+
"utf8",
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
// Split the source at the resumeClaudeTask function boundary. The first
|
|
12
|
+
// half contains executeClaudeTask's query() block; the second half contains
|
|
13
|
+
// resumeClaudeTask's query() block. This is more robust than a single
|
|
14
|
+
// regex over the whole file — a future edit that adds a stray `canUseTool`
|
|
15
|
+
// reference above either function won't cause a parity test to match the
|
|
16
|
+
// wrong query() call.
|
|
17
|
+
const resumeMarker = "export async function resumeClaudeTask";
|
|
18
|
+
const splitIndex = agentSource.indexOf(resumeMarker);
|
|
19
|
+
if (splitIndex === -1) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
"claude-agent-sdk-options.test.ts: could not find `" + resumeMarker +
|
|
22
|
+
"` in claude-agent.ts — rename or refactor broke this test's assumptions",
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
const executeSection = agentSource.slice(0, splitIndex);
|
|
26
|
+
const resumeSection = agentSource.slice(splitIndex);
|
|
27
|
+
|
|
28
|
+
it("imports CLAUDE_SDK_ALLOWED_TOOLS from runtime/claude-sdk", () => {
|
|
29
|
+
expect(agentSource).toMatch(/CLAUDE_SDK_ALLOWED_TOOLS[\s\S]*runtime\/claude-sdk/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("imports CLAUDE_SDK_SETTING_SOURCES from runtime/claude-sdk", () => {
|
|
33
|
+
expect(agentSource).toMatch(/CLAUDE_SDK_SETTING_SOURCES[\s\S]*runtime\/claude-sdk/);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("imports getFeaturesForModel to gate native-skill options", () => {
|
|
37
|
+
expect(agentSource).toMatch(/getFeaturesForModel/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("passes settingSources inside executeClaudeTask query() options", () => {
|
|
41
|
+
// The execute section must contain a query( call AND settingSources.
|
|
42
|
+
expect(executeSection).toMatch(/query\(/);
|
|
43
|
+
expect(executeSection).toContain("settingSources");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("passes settingSources inside resumeClaudeTask query() options", () => {
|
|
47
|
+
expect(resumeSection).toMatch(/query\(/);
|
|
48
|
+
expect(resumeSection).toContain("settingSources");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("hooks field is NOT present in either query() options block", () => {
|
|
52
|
+
for (const section of [executeSection, resumeSection]) {
|
|
53
|
+
expect(section).not.toMatch(/\bhooks\s*:/);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -141,10 +141,16 @@ vi.mock("@/lib/agents/browser-mcp", () => ({
|
|
|
141
141
|
isExaTool: vi.fn().mockReturnValue(false),
|
|
142
142
|
isExaReadOnly: vi.fn().mockReturnValue(false),
|
|
143
143
|
}));
|
|
144
|
+
vi.mock("@/lib/chat/stagent-tools", () => ({
|
|
145
|
+
createToolServer: vi.fn((_projectId?: string | null) => ({
|
|
146
|
+
asMcpServer: () => ({ __mockStagentServer: true }),
|
|
147
|
+
})),
|
|
148
|
+
}));
|
|
144
149
|
|
|
145
150
|
// Static imports (works because vi.mock is hoisted)
|
|
146
151
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
147
152
|
import { executeClaudeTask, resumeClaudeTask } from "../claude-agent";
|
|
153
|
+
import { createToolServer } from "@/lib/chat/stagent-tools";
|
|
148
154
|
|
|
149
155
|
const mockQuery = vi.mocked(query);
|
|
150
156
|
|
|
@@ -238,6 +244,85 @@ describe("executeClaudeTask", () => {
|
|
|
238
244
|
expect(mockRemoveExecution).toHaveBeenCalledWith("task-1");
|
|
239
245
|
});
|
|
240
246
|
|
|
247
|
+
it("A-stagent-1: injects stagent MCP server into query mcpServers", async () => {
|
|
248
|
+
mockWhere.mockResolvedValueOnce([makeTask({ projectId: "proj-7" })]);
|
|
249
|
+
mockQuery.mockReturnValue(
|
|
250
|
+
createMockStream([
|
|
251
|
+
{ type: "result", result: "done" },
|
|
252
|
+
]) as unknown as ReturnType<typeof query>
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
await executeClaudeTask("task-1");
|
|
256
|
+
|
|
257
|
+
const queryCall = mockQuery.mock.calls[0][0] as {
|
|
258
|
+
options: { mcpServers?: Record<string, unknown> };
|
|
259
|
+
};
|
|
260
|
+
expect(queryCall.options.mcpServers).toBeDefined();
|
|
261
|
+
expect(queryCall.options.mcpServers!.stagent).toEqual({ __mockStagentServer: true });
|
|
262
|
+
expect(vi.mocked(createToolServer)).toHaveBeenCalledWith("proj-7");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("A-stagent-2: prepends mcp__stagent__* when profile has allowedTools", async () => {
|
|
266
|
+
mockWhere.mockResolvedValueOnce([makeTask({ projectId: "proj-7" })]);
|
|
267
|
+
mockGetProfile.mockReturnValueOnce({
|
|
268
|
+
id: "restricted",
|
|
269
|
+
name: "Restricted",
|
|
270
|
+
systemPrompt: "",
|
|
271
|
+
allowedTools: ["Read", "Grep"],
|
|
272
|
+
});
|
|
273
|
+
mockQuery.mockReturnValue(
|
|
274
|
+
createMockStream([
|
|
275
|
+
{ type: "result", result: "done" },
|
|
276
|
+
]) as unknown as ReturnType<typeof query>
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
await executeClaudeTask("task-1");
|
|
280
|
+
|
|
281
|
+
const queryCall = mockQuery.mock.calls[0][0] as {
|
|
282
|
+
options: { allowedTools?: string[] };
|
|
283
|
+
};
|
|
284
|
+
expect(queryCall.options.allowedTools).toBeDefined();
|
|
285
|
+
expect(queryCall.options.allowedTools).toContain("mcp__stagent__*");
|
|
286
|
+
expect(queryCall.options.allowedTools).toContain("Read");
|
|
287
|
+
expect(queryCall.options.allowedTools).toContain("Grep");
|
|
288
|
+
// Duplicates not added when profile didn't already include the pattern
|
|
289
|
+
const stagentCount = queryCall.options.allowedTools!.filter(
|
|
290
|
+
(t) => t === "mcp__stagent__*"
|
|
291
|
+
).length;
|
|
292
|
+
expect(stagentCount).toBe(1);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("A-stagent-3: falls back to CLAUDE_SDK_ALLOWED_TOOLS when profile has none and runtime has native skills", async () => {
|
|
296
|
+
mockWhere.mockResolvedValueOnce([makeTask({ projectId: "proj-7" })]);
|
|
297
|
+
// Default mockGetProfile returns allowedTools: undefined. Task-runtime-skill-parity
|
|
298
|
+
// (Task 3) changed withStagentAllowedTools so the Phase 1a tool set (Skill,
|
|
299
|
+
// Read/Grep/Glob, Edit/Write/Bash, TodoWrite) is passed alongside mcp__stagent__*
|
|
300
|
+
// when the runtime has hasNativeSkills=true — which is the claude-code default.
|
|
301
|
+
mockQuery.mockReturnValue(
|
|
302
|
+
createMockStream([
|
|
303
|
+
{ type: "result", result: "done" },
|
|
304
|
+
]) as unknown as ReturnType<typeof query>
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
await executeClaudeTask("task-1");
|
|
308
|
+
|
|
309
|
+
const queryCall = mockQuery.mock.calls[0][0] as {
|
|
310
|
+
options: { allowedTools?: string[] };
|
|
311
|
+
};
|
|
312
|
+
expect(queryCall.options.allowedTools).toBeDefined();
|
|
313
|
+
expect(queryCall.options.allowedTools).toEqual([
|
|
314
|
+
"mcp__stagent__*",
|
|
315
|
+
"Skill",
|
|
316
|
+
"Read",
|
|
317
|
+
"Grep",
|
|
318
|
+
"Glob",
|
|
319
|
+
"Edit",
|
|
320
|
+
"Write",
|
|
321
|
+
"Bash",
|
|
322
|
+
"TodoWrite",
|
|
323
|
+
]);
|
|
324
|
+
});
|
|
325
|
+
|
|
241
326
|
it("A3: captures sessionId from init message and re-calls setExecution", async () => {
|
|
242
327
|
mockWhere.mockResolvedValueOnce([makeTask()]);
|
|
243
328
|
mockQuery.mockReturnValue(
|
|
@@ -337,6 +422,51 @@ describe("executeClaudeTask", () => {
|
|
|
337
422
|
expect(callOptions.maxTurns).toBeDefined();
|
|
338
423
|
expect(callOptions.maxBudgetUsd).toBeDefined();
|
|
339
424
|
});
|
|
425
|
+
|
|
426
|
+
it("A8: waits for learned-pattern extraction before final cleanup", async () => {
|
|
427
|
+
let resolveAnalysis: (() => void) | null = null;
|
|
428
|
+
mockWhere.mockResolvedValueOnce([makeTask()]);
|
|
429
|
+
mockQuery.mockReturnValue(
|
|
430
|
+
createMockStream([{ type: "result", result: "done" }]) as unknown as ReturnType<typeof query>
|
|
431
|
+
);
|
|
432
|
+
mockAnalyzeForLearnedPatterns.mockReturnValueOnce(
|
|
433
|
+
new Promise((resolve) => {
|
|
434
|
+
resolveAnalysis = () => resolve(null);
|
|
435
|
+
})
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
const runPromise = executeClaudeTask("task-1");
|
|
439
|
+
await vi.waitFor(() => {
|
|
440
|
+
expect(mockAnalyzeForLearnedPatterns).toHaveBeenCalledWith("task-1", "general");
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
expect(mockRemoveExecution).not.toHaveBeenCalled();
|
|
444
|
+
|
|
445
|
+
resolveAnalysis?.();
|
|
446
|
+
await runPromise;
|
|
447
|
+
|
|
448
|
+
expect(mockRemoveExecution).toHaveBeenCalledWith("task-1");
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("A9: logs learned-pattern extraction failures without failing the task", async () => {
|
|
452
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
453
|
+
mockWhere.mockResolvedValueOnce([makeTask()]);
|
|
454
|
+
mockQuery.mockReturnValue(
|
|
455
|
+
createMockStream([{ type: "result", result: "done" }]) as unknown as ReturnType<typeof query>
|
|
456
|
+
);
|
|
457
|
+
mockAnalyzeForLearnedPatterns.mockRejectedValueOnce(new Error("extract failed"));
|
|
458
|
+
|
|
459
|
+
await executeClaudeTask("task-1");
|
|
460
|
+
|
|
461
|
+
expect(mockSet).toHaveBeenCalledWith(
|
|
462
|
+
expect.objectContaining({ status: "completed", result: "done" })
|
|
463
|
+
);
|
|
464
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
465
|
+
"[self-improvement] pattern extraction failed:",
|
|
466
|
+
expect.any(Error)
|
|
467
|
+
);
|
|
468
|
+
errorSpy.mockRestore();
|
|
469
|
+
});
|
|
340
470
|
});
|
|
341
471
|
|
|
342
472
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -527,6 +657,88 @@ describe("resumeClaudeTask", () => {
|
|
|
527
657
|
expect.objectContaining({ event: "error" })
|
|
528
658
|
);
|
|
529
659
|
});
|
|
660
|
+
|
|
661
|
+
it("C5: waits for learned-pattern extraction before final cleanup on resume", async () => {
|
|
662
|
+
let resolveAnalysis: (() => void) | null = null;
|
|
663
|
+
mockWhere.mockResolvedValueOnce([
|
|
664
|
+
makeTask({ sessionId: "sess-123", resumeCount: 0 }),
|
|
665
|
+
]);
|
|
666
|
+
mockQuery.mockReturnValue(
|
|
667
|
+
createMockStream([{ type: "result", result: "resumed ok" }]) as unknown as ReturnType<typeof query>
|
|
668
|
+
);
|
|
669
|
+
mockAnalyzeForLearnedPatterns.mockReturnValueOnce(
|
|
670
|
+
new Promise((resolve) => {
|
|
671
|
+
resolveAnalysis = () => resolve(null);
|
|
672
|
+
})
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
const runPromise = resumeClaudeTask("task-1");
|
|
676
|
+
await vi.waitFor(() => {
|
|
677
|
+
expect(mockAnalyzeForLearnedPatterns).toHaveBeenCalledWith("task-1", "general");
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
expect(mockRemoveExecution).not.toHaveBeenCalled();
|
|
681
|
+
|
|
682
|
+
resolveAnalysis?.();
|
|
683
|
+
await runPromise;
|
|
684
|
+
|
|
685
|
+
expect(mockRemoveExecution).toHaveBeenCalledWith("task-1");
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it("R-stagent-1: injects stagent MCP server into query mcpServers on resume", async () => {
|
|
689
|
+
mockWhere.mockResolvedValueOnce([
|
|
690
|
+
makeTask({
|
|
691
|
+
projectId: "proj-7",
|
|
692
|
+
sessionId: "session-abc",
|
|
693
|
+
resumeCount: 1,
|
|
694
|
+
}),
|
|
695
|
+
]);
|
|
696
|
+
mockQuery.mockReturnValue(
|
|
697
|
+
createMockStream([
|
|
698
|
+
{ type: "result", result: "resumed and done" },
|
|
699
|
+
]) as unknown as ReturnType<typeof query>
|
|
700
|
+
);
|
|
701
|
+
|
|
702
|
+
await resumeClaudeTask("task-1");
|
|
703
|
+
|
|
704
|
+
const queryCall = mockQuery.mock.calls[0][0] as {
|
|
705
|
+
options: { mcpServers?: Record<string, unknown>; resume?: string };
|
|
706
|
+
};
|
|
707
|
+
expect(queryCall.options.resume).toBe("session-abc");
|
|
708
|
+
expect(queryCall.options.mcpServers).toBeDefined();
|
|
709
|
+
expect(queryCall.options.mcpServers!.stagent).toEqual({ __mockStagentServer: true });
|
|
710
|
+
expect(vi.mocked(createToolServer)).toHaveBeenCalledWith("proj-7");
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it("R-stagent-2: prepends mcp__stagent__* on resume when profile has allowedTools", async () => {
|
|
714
|
+
mockWhere.mockResolvedValueOnce([
|
|
715
|
+
makeTask({
|
|
716
|
+
projectId: "proj-7",
|
|
717
|
+
sessionId: "session-abc",
|
|
718
|
+
resumeCount: 1,
|
|
719
|
+
}),
|
|
720
|
+
]);
|
|
721
|
+
mockGetProfile.mockReturnValueOnce({
|
|
722
|
+
id: "restricted",
|
|
723
|
+
name: "Restricted",
|
|
724
|
+
systemPrompt: "",
|
|
725
|
+
allowedTools: ["Read", "Grep"],
|
|
726
|
+
});
|
|
727
|
+
mockQuery.mockReturnValue(
|
|
728
|
+
createMockStream([
|
|
729
|
+
{ type: "result", result: "resumed and done" },
|
|
730
|
+
]) as unknown as ReturnType<typeof query>
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
await resumeClaudeTask("task-1");
|
|
734
|
+
|
|
735
|
+
const queryCall = mockQuery.mock.calls[0][0] as {
|
|
736
|
+
options: { allowedTools?: string[] };
|
|
737
|
+
};
|
|
738
|
+
expect(queryCall.options.allowedTools).toContain("mcp__stagent__*");
|
|
739
|
+
expect(queryCall.options.allowedTools).toContain("Read");
|
|
740
|
+
expect(queryCall.options.allowedTools![0]).toBe("mcp__stagent__*");
|
|
741
|
+
});
|
|
530
742
|
});
|
|
531
743
|
|
|
532
744
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach
|
|
2
|
-
|
|
3
|
-
vi.mock("@/lib/license/manager", () => ({
|
|
4
|
-
licenseManager: {
|
|
5
|
-
getLimit: vi.fn().mockReturnValue(Infinity),
|
|
6
|
-
getTier: vi.fn().mockReturnValue("scale"),
|
|
7
|
-
},
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
vi.mock("@/lib/license/notifications", () => ({
|
|
11
|
-
createTierLimitNotification: vi.fn().mockResolvedValue(undefined),
|
|
12
|
-
}));
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
13
2
|
|
|
14
3
|
import {
|
|
15
4
|
getExecution,
|
|
16
5
|
setExecution,
|
|
17
6
|
removeExecution,
|
|
18
7
|
getAllExecutions,
|
|
19
|
-
ParallelLimitError,
|
|
20
8
|
} from "@/lib/agents/execution-manager";
|
|
21
9
|
|
|
22
10
|
function makeExecution(taskId: string) {
|
|
@@ -73,18 +61,4 @@ describe("execution-manager", () => {
|
|
|
73
61
|
it("removing non-existent task does not throw", () => {
|
|
74
62
|
expect(() => removeExecution("nonexistent")).not.toThrow();
|
|
75
63
|
});
|
|
76
|
-
|
|
77
|
-
it("throws ParallelLimitError when limit is reached", async () => {
|
|
78
|
-
const { licenseManager } = await import("@/lib/license/manager");
|
|
79
|
-
(licenseManager.getLimit as ReturnType<typeof vi.fn>).mockReturnValue(2);
|
|
80
|
-
(licenseManager.getTier as ReturnType<typeof vi.fn>).mockReturnValue("community");
|
|
81
|
-
|
|
82
|
-
setExecution("task-1", makeExecution("task-1"));
|
|
83
|
-
setExecution("task-2", makeExecution("task-2"));
|
|
84
|
-
|
|
85
|
-
expect(() => setExecution("task-3", makeExecution("task-3"))).toThrow(ParallelLimitError);
|
|
86
|
-
|
|
87
|
-
// Restore unlimited for other tests
|
|
88
|
-
(licenseManager.getLimit as ReturnType<typeof vi.fn>).mockReturnValue(Infinity);
|
|
89
|
-
});
|
|
90
64
|
});
|