stagent 0.9.6 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -44
- package/dist/cli.js +66 -18
- package/docs/.coverage-gaps.json +144 -56
- package/docs/.last-generated +1 -1
- package/docs/features/agent-intelligence.md +12 -2
- package/docs/features/chat.md +40 -5
- package/docs/features/cost-usage.md +1 -1
- package/docs/features/documents.md +5 -2
- package/docs/features/inbox-notifications.md +10 -2
- package/docs/features/keyboard-navigation.md +12 -3
- package/docs/features/provider-runtimes.md +20 -2
- package/docs/features/schedules.md +32 -4
- package/docs/features/settings.md +28 -5
- package/docs/features/shared-components.md +7 -3
- package/docs/features/tables.md +11 -2
- package/docs/features/tool-permissions.md +6 -2
- package/docs/features/workflows.md +14 -4
- package/docs/index.md +1 -1
- package/docs/journeys/developer.md +39 -2
- package/docs/journeys/personal-use.md +32 -8
- package/docs/journeys/power-user.md +45 -14
- package/docs/journeys/work-use.md +17 -8
- package/docs/manifest.json +15 -15
- package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
- package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
- package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
- package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
- package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
- package/docs/superpowers/plans/2026-04-14-chat-command-namespace-refactor.md +1390 -0
- package/docs/superpowers/plans/2026-04-14-chat-environment-integration.md +1561 -0
- package/docs/superpowers/plans/2026-04-14-chat-polish-bundle-v1.md +1219 -0
- package/docs/superpowers/plans/2026-04-14-chat-session-persistence-provider-closeout.md +399 -0
- package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
- package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
- package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
- package/next.config.mjs +1 -0
- package/package.json +3 -2
- package/src/__tests__/instrumentation-smoke.test.ts +15 -0
- package/src/app/analytics/page.tsx +1 -21
- package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
- package/src/app/api/chat/conversations/[id]/skills/__tests__/activate.test.ts +141 -0
- package/src/app/api/chat/conversations/[id]/skills/activate/route.ts +74 -0
- package/src/app/api/chat/conversations/[id]/skills/deactivate/route.ts +33 -0
- package/src/app/api/chat/export/route.ts +52 -0
- package/src/app/api/chat/files/search/route.ts +50 -0
- package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
- package/src/app/api/environment/rescan-if-stale/__tests__/route.test.ts +45 -0
- package/src/app/api/environment/rescan-if-stale/route.ts +23 -0
- package/src/app/api/environment/skills/route.ts +13 -0
- package/src/app/api/instance/config/route.ts +41 -0
- package/src/app/api/instance/init/route.ts +34 -0
- package/src/app/api/instance/upgrade/check/route.ts +26 -0
- package/src/app/api/instance/upgrade/route.ts +96 -0
- package/src/app/api/instance/upgrade/status/route.ts +35 -0
- package/src/app/api/memory/route.ts +0 -11
- package/src/app/api/notifications/route.ts +4 -2
- package/src/app/api/projects/[id]/route.ts +5 -155
- package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
- package/src/app/api/schedules/[id]/execute/route.ts +111 -0
- package/src/app/api/schedules/[id]/route.ts +9 -1
- package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
- package/src/app/api/schedules/route.ts +3 -12
- package/src/app/api/settings/chat/pins/route.ts +94 -0
- package/src/app/api/settings/chat/saved-searches/__tests__/route.test.ts +119 -0
- package/src/app/api/settings/chat/saved-searches/route.ts +79 -0
- package/src/app/api/settings/environment/route.ts +26 -0
- package/src/app/api/settings/openai/login/route.ts +22 -0
- package/src/app/api/settings/openai/logout/route.ts +7 -0
- package/src/app/api/settings/openai/route.ts +21 -1
- package/src/app/api/settings/providers/route.ts +35 -8
- package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
- package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
- package/src/app/api/tables/[id]/enrich/route.ts +147 -0
- package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
- package/src/app/api/tasks/[id]/execute/route.ts +52 -33
- package/src/app/api/tasks/[id]/respond/route.ts +31 -15
- package/src/app/api/tasks/[id]/resume/route.ts +24 -3
- package/src/app/api/workflows/[id]/resume/route.ts +59 -0
- package/src/app/api/workflows/[id]/status/route.ts +22 -8
- package/src/app/api/workspace/context/route.ts +2 -0
- package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
- package/src/app/chat/page.tsx +11 -0
- package/src/app/documents/page.tsx +4 -1
- package/src/app/inbox/page.tsx +12 -5
- package/src/app/layout.tsx +42 -21
- package/src/app/page.tsx +0 -2
- package/src/app/settings/page.tsx +8 -9
- package/src/components/chat/__tests__/capability-banner.test.tsx +38 -0
- package/src/components/chat/__tests__/chat-session-provider.test.tsx +573 -0
- package/src/components/chat/__tests__/skill-row.test.tsx +91 -0
- package/src/components/chat/capability-banner.tsx +68 -0
- package/src/components/chat/chat-command-popover.tsx +670 -49
- package/src/components/chat/chat-input.tsx +104 -10
- package/src/components/chat/chat-message.tsx +12 -3
- package/src/components/chat/chat-session-provider.tsx +790 -0
- package/src/components/chat/chat-shell.tsx +151 -401
- package/src/components/chat/command-tab-bar.tsx +68 -0
- package/src/components/chat/conversation-template-picker.tsx +421 -0
- package/src/components/chat/help-dialog.tsx +39 -0
- package/src/components/chat/skill-composition-conflict-dialog.tsx +96 -0
- package/src/components/chat/skill-row.tsx +147 -0
- package/src/components/documents/document-browser.tsx +37 -19
- package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
- package/src/components/instance/instance-section.tsx +382 -0
- package/src/components/instance/upgrade-badge.tsx +219 -0
- package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
- package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
- package/src/components/notifications/__tests__/permission-response-actions.test.tsx +70 -0
- package/src/components/notifications/batch-proposal-review.tsx +20 -5
- package/src/components/notifications/inbox-list.tsx +11 -2
- package/src/components/notifications/notification-item.tsx +56 -2
- package/src/components/notifications/pending-approval-host.tsx +56 -37
- package/src/components/notifications/permission-response-actions.tsx +155 -1
- package/src/components/schedules/schedule-create-sheet.tsx +19 -1
- package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
- package/src/components/schedules/schedule-form.tsx +31 -0
- package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
- package/src/components/settings/auth-method-selector.tsx +19 -4
- package/src/components/settings/auth-status-badge.tsx +28 -3
- package/src/components/settings/environment-section.tsx +102 -0
- package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
- package/src/components/settings/openai-runtime-section.tsx +7 -1
- package/src/components/settings/providers-runtimes-section.tsx +138 -19
- package/src/components/shared/__tests__/filter-hint.test.tsx +40 -0
- package/src/components/shared/__tests__/saved-searches-manager.test.tsx +147 -0
- package/src/components/shared/app-sidebar.tsx +4 -3
- package/src/components/shared/command-palette.tsx +266 -7
- package/src/components/shared/filter-hint.tsx +70 -0
- package/src/components/shared/filter-input.tsx +59 -0
- package/src/components/shared/saved-searches-manager.tsx +199 -0
- package/src/components/shared/theme-toggle.tsx +5 -24
- package/src/components/shared/workspace-indicator.tsx +61 -2
- package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
- package/src/components/tables/table-create-sheet.tsx +4 -0
- package/src/components/tables/table-enrichment-runs.tsx +103 -0
- package/src/components/tables/table-enrichment-sheet.tsx +538 -0
- package/src/components/tables/table-spreadsheet.tsx +29 -5
- package/src/components/tables/table-toolbar.tsx +10 -1
- package/src/components/tasks/kanban-board.tsx +1 -0
- package/src/components/tasks/kanban-column.tsx +53 -14
- package/src/components/tasks/task-bento-grid.tsx +31 -2
- package/src/components/tasks/task-card.tsx +29 -3
- package/src/components/tasks/task-chip-bar.tsx +54 -1
- package/src/components/tasks/task-result-renderer.tsx +1 -1
- package/src/components/workflows/delay-step-body.tsx +109 -0
- package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
- package/src/components/workflows/loop-status-view.tsx +1 -1
- package/src/components/workflows/shared/step-result.tsx +78 -0
- package/src/components/workflows/shared/workflow-header.tsx +141 -0
- package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
- package/src/components/workflows/swarm-dashboard.tsx +2 -15
- package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
- package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
- package/src/components/workflows/workflow-form-view.tsx +133 -16
- package/src/components/workflows/workflow-status-view.tsx +30 -740
- package/src/hooks/__tests__/use-chat-autocomplete-tabs.test.ts +47 -0
- package/src/hooks/__tests__/use-saved-searches.test.ts +70 -0
- package/src/hooks/use-active-skills.ts +110 -0
- package/src/hooks/use-chat-autocomplete.ts +120 -7
- package/src/hooks/use-enriched-skills.ts +19 -0
- package/src/hooks/use-pinned-entries.ts +104 -0
- package/src/hooks/use-recent-user-messages.ts +19 -0
- package/src/hooks/use-saved-searches.ts +142 -0
- package/src/instrumentation-node.ts +94 -0
- package/src/instrumentation.ts +4 -48
- package/src/lib/agents/__tests__/claude-agent-sdk-options.test.ts +56 -0
- package/src/lib/agents/__tests__/claude-agent.test.ts +212 -0
- package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
- package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
- package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
- package/src/lib/agents/__tests__/task-dispatch.test.ts +166 -0
- package/src/lib/agents/__tests__/tool-permissions.test.ts +60 -0
- package/src/lib/agents/claude-agent.ts +217 -21
- package/src/lib/agents/execution-manager.ts +0 -35
- package/src/lib/agents/handoff/bus.ts +2 -2
- package/src/lib/agents/learned-context.ts +0 -12
- package/src/lib/agents/learning-session.ts +18 -5
- package/src/lib/agents/profiles/__tests__/list-fused-profiles.test.ts +110 -0
- package/src/lib/agents/profiles/__tests__/registry.test.ts +53 -4
- package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +97 -0
- package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +36 -0
- package/src/lib/agents/profiles/list-fused-profiles.ts +104 -0
- package/src/lib/agents/profiles/registry.ts +18 -0
- package/src/lib/agents/profiles/types.ts +7 -1
- package/src/lib/agents/router.ts +3 -6
- package/src/lib/agents/runtime/__tests__/catalog.test.ts +130 -0
- package/src/lib/agents/runtime/__tests__/execution-target.test.ts +183 -0
- package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
- package/src/lib/agents/runtime/anthropic-direct.ts +8 -0
- package/src/lib/agents/runtime/catalog.ts +121 -0
- package/src/lib/agents/runtime/claude-sdk.ts +32 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
- package/src/lib/agents/runtime/execution-target.ts +456 -0
- package/src/lib/agents/runtime/index.ts +4 -0
- package/src/lib/agents/runtime/launch-failure.ts +101 -0
- package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
- package/src/lib/agents/runtime/openai-codex.ts +64 -60
- package/src/lib/agents/runtime/openai-direct.ts +8 -0
- package/src/lib/agents/runtime/types.ts +8 -0
- package/src/lib/agents/task-dispatch.ts +220 -0
- package/src/lib/agents/tool-permissions.ts +16 -1
- package/src/lib/book/chapter-mapping.ts +11 -0
- package/src/lib/book/content.ts +10 -0
- package/src/lib/chat/__tests__/active-skill-injection.test.ts +261 -0
- package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
- package/src/lib/chat/__tests__/clean-filter-input.test.ts +68 -0
- package/src/lib/chat/__tests__/command-tabs.test.ts +68 -0
- package/src/lib/chat/__tests__/context-builder-files.test.ts +112 -0
- package/src/lib/chat/__tests__/dismissals.test.ts +65 -0
- package/src/lib/chat/__tests__/engine-sdk-options.test.ts +117 -0
- package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
- package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
- package/src/lib/chat/__tests__/skill-conflict.test.ts +35 -0
- package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
- package/src/lib/chat/__tests__/types.test.ts +28 -0
- package/src/lib/chat/active-skills.ts +31 -0
- package/src/lib/chat/active-streams.ts +27 -0
- package/src/lib/chat/clean-filter-input.ts +30 -0
- package/src/lib/chat/codex-engine.ts +46 -24
- package/src/lib/chat/command-tabs.ts +61 -0
- package/src/lib/chat/context-builder.ts +146 -4
- package/src/lib/chat/dismissals.ts +73 -0
- package/src/lib/chat/engine.ts +159 -18
- package/src/lib/chat/files/__tests__/search.test.ts +135 -0
- package/src/lib/chat/files/expand-mention.ts +76 -0
- package/src/lib/chat/files/search.ts +99 -0
- package/src/lib/chat/reconcile.ts +117 -0
- package/src/lib/chat/skill-composition.ts +210 -0
- package/src/lib/chat/skill-conflict.ts +105 -0
- package/src/lib/chat/stagent-tools.ts +7 -19
- package/src/lib/chat/stream-telemetry.ts +137 -0
- package/src/lib/chat/suggested-prompts.ts +28 -1
- package/src/lib/chat/system-prompt.ts +48 -1
- package/src/lib/chat/tool-catalog.ts +35 -4
- package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
- package/src/lib/chat/tools/__tests__/profile-tools.test.ts +51 -0
- package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
- package/src/lib/chat/tools/__tests__/settings-tools.test.ts +294 -0
- package/src/lib/chat/tools/__tests__/skill-tools.test.ts +474 -0
- package/src/lib/chat/tools/__tests__/task-tools.test.ts +399 -0
- package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +351 -0
- package/src/lib/chat/tools/blueprint-tools.ts +190 -0
- package/src/lib/chat/tools/document-tools.ts +29 -13
- package/src/lib/chat/tools/helpers.ts +41 -0
- package/src/lib/chat/tools/notification-tools.ts +9 -5
- package/src/lib/chat/tools/profile-tools.ts +120 -23
- package/src/lib/chat/tools/project-tools.ts +33 -0
- package/src/lib/chat/tools/schedule-tools.ts +44 -11
- package/src/lib/chat/tools/skill-tools.ts +183 -0
- package/src/lib/chat/tools/table-tools.ts +71 -0
- package/src/lib/chat/tools/task-tools.ts +89 -21
- package/src/lib/chat/tools/workflow-tools.ts +275 -32
- package/src/lib/chat/types.ts +15 -0
- package/src/lib/constants/settings.ts +10 -18
- package/src/lib/data/__tests__/clear.test.ts +56 -2
- package/src/lib/data/clear.ts +17 -16
- package/src/lib/data/delete-project.ts +171 -0
- package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
- package/src/lib/db/bootstrap.ts +62 -16
- package/src/lib/db/index.ts +5 -0
- package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
- package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
- package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
- package/src/lib/db/migrations/0026_drop_license.sql +3 -0
- package/src/lib/db/migrations/meta/_journal.json +21 -0
- package/src/lib/db/schema.ts +94 -23
- package/src/lib/environment/__tests__/auto-promote.test.ts +132 -0
- package/src/lib/environment/__tests__/list-skills-enriched.test.ts +55 -0
- package/src/lib/environment/__tests__/skill-enrichment.test.ts +129 -0
- package/src/lib/environment/__tests__/skill-recommendations.test.ts +87 -0
- package/src/lib/environment/data.ts +9 -0
- package/src/lib/environment/list-skills.ts +176 -0
- package/src/lib/environment/parsers/__tests__/skill.test.ts +54 -0
- package/src/lib/environment/parsers/skill.ts +26 -5
- package/src/lib/environment/profile-generator.ts +54 -0
- package/src/lib/environment/skill-enrichment.ts +106 -0
- package/src/lib/environment/skill-recommendations.ts +66 -0
- package/src/lib/environment/workspace-context.ts +13 -1
- package/src/lib/filters/__tests__/parse.quoted.test.ts +40 -0
- package/src/lib/filters/__tests__/parse.test.ts +135 -0
- package/src/lib/filters/parse.ts +86 -0
- package/src/lib/import/dedup.ts +4 -54
- package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
- package/src/lib/instance/__tests__/detect.test.ts +115 -0
- package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
- package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
- package/src/lib/instance/__tests__/settings.test.ts +83 -0
- package/src/lib/instance/__tests__/upgrade-poller.test.ts +181 -0
- package/src/lib/instance/bootstrap.ts +270 -0
- package/src/lib/instance/detect.ts +49 -0
- package/src/lib/instance/fingerprint.ts +76 -0
- package/src/lib/instance/git-ops.ts +95 -0
- package/src/lib/instance/settings.ts +61 -0
- package/src/lib/instance/types.ts +77 -0
- package/src/lib/instance/upgrade-poller.ts +205 -0
- package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
- package/src/lib/notifications/visibility.ts +33 -0
- package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
- package/src/lib/schedules/__tests__/config.test.ts +62 -0
- package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
- package/src/lib/schedules/__tests__/integration.test.ts +82 -0
- package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
- package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
- package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
- package/src/lib/schedules/collision-check.ts +105 -0
- package/src/lib/schedules/config.ts +53 -0
- package/src/lib/schedules/scheduler.ts +236 -17
- package/src/lib/schedules/slot-claim.ts +105 -0
- package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
- package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
- package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
- package/src/lib/settings/openai-auth.ts +105 -10
- package/src/lib/settings/openai-login-manager.ts +260 -0
- package/src/lib/settings/runtime-setup.ts +14 -4
- package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
- package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
- package/src/lib/tables/enrichment-planner.ts +454 -0
- package/src/lib/tables/enrichment.ts +328 -0
- package/src/lib/tables/query-builder.ts +5 -2
- package/src/lib/tables/trigger-evaluator.ts +3 -2
- package/src/lib/theme.ts +71 -0
- package/src/lib/usage/ledger.ts +2 -18
- package/src/lib/util/__tests__/similarity.test.ts +106 -0
- package/src/lib/util/similarity.ts +77 -0
- package/src/lib/utils/format-timestamp.ts +24 -0
- package/src/lib/utils/stagent-paths.ts +12 -0
- package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
- package/src/lib/validators/__tests__/settings.test.ts +10 -0
- package/src/lib/validators/blueprint.ts +70 -9
- package/src/lib/validators/profile.ts +2 -2
- package/src/lib/validators/settings.ts +3 -1
- package/src/lib/workflows/__tests__/delay.test.ts +196 -0
- package/src/lib/workflows/__tests__/engine.test.ts +8 -0
- package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
- package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
- package/src/lib/workflows/blueprints/__tests__/render-prompt.test.ts +124 -0
- package/src/lib/workflows/blueprints/instantiator.ts +22 -1
- package/src/lib/workflows/blueprints/render-prompt.ts +71 -0
- package/src/lib/workflows/blueprints/types.ts +16 -2
- package/src/lib/workflows/delay.ts +106 -0
- package/src/lib/workflows/engine.ts +212 -7
- package/src/lib/workflows/loop-executor.ts +349 -24
- package/src/lib/workflows/post-action.ts +91 -0
- package/src/lib/workflows/types.ts +166 -1
- package/src/test/setup.ts +10 -0
- package/src/app/api/license/checkout/route.ts +0 -28
- package/src/app/api/license/portal/route.ts +0 -26
- package/src/app/api/license/route.ts +0 -89
- package/src/app/api/license/usage/route.ts +0 -63
- package/src/app/api/marketplace/browse/route.ts +0 -15
- package/src/app/api/marketplace/import/route.ts +0 -28
- package/src/app/api/marketplace/publish/route.ts +0 -40
- package/src/app/api/onboarding/email/route.ts +0 -53
- package/src/app/api/settings/telemetry/route.ts +0 -14
- package/src/app/api/sync/export/route.ts +0 -54
- package/src/app/api/sync/restore/route.ts +0 -37
- package/src/app/api/sync/sessions/route.ts +0 -24
- package/src/app/auth/callback/route.ts +0 -73
- package/src/app/marketplace/page.tsx +0 -19
- package/src/components/analytics/analytics-gate-card.tsx +0 -101
- package/src/components/marketplace/blueprint-card.tsx +0 -61
- package/src/components/marketplace/marketplace-browser.tsx +0 -131
- package/src/components/onboarding/email-capture-card.tsx +0 -104
- package/src/components/settings/activation-form.tsx +0 -95
- package/src/components/settings/cloud-account-section.tsx +0 -147
- package/src/components/settings/cloud-sync-section.tsx +0 -155
- package/src/components/settings/subscription-section.tsx +0 -410
- package/src/components/settings/telemetry-section.tsx +0 -80
- package/src/components/shared/premium-gate-overlay.tsx +0 -50
- package/src/components/shared/schedule-gate-dialog.tsx +0 -64
- package/src/components/shared/upgrade-banner.tsx +0 -112
- package/src/hooks/use-supabase-auth.ts +0 -79
- package/src/lib/billing/email.ts +0 -54
- package/src/lib/billing/products.ts +0 -80
- package/src/lib/billing/stripe.ts +0 -101
- package/src/lib/cloud/supabase-browser.ts +0 -32
- package/src/lib/cloud/supabase-client.ts +0 -56
- package/src/lib/license/__tests__/features.test.ts +0 -56
- package/src/lib/license/__tests__/key-format.test.ts +0 -88
- package/src/lib/license/__tests__/manager.test.ts +0 -64
- package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
- package/src/lib/license/cloud-validation.ts +0 -60
- package/src/lib/license/features.ts +0 -44
- package/src/lib/license/key-format.ts +0 -101
- package/src/lib/license/limit-check.ts +0 -111
- package/src/lib/license/limit-queries.ts +0 -51
- package/src/lib/license/manager.ts +0 -345
- package/src/lib/license/notifications.ts +0 -59
- package/src/lib/license/tier-limits.ts +0 -71
- package/src/lib/marketplace/marketplace-client.ts +0 -107
- package/src/lib/sync/cloud-sync.ts +0 -235
- package/src/lib/telemetry/conversion-events.ts +0 -71
- package/src/lib/telemetry/queue.ts +0 -122
- package/src/lib/validators/license.ts +0 -33
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Enhanced system prompt for the Stagent chat LLM.
|
|
3
3
|
* Provides identity, tool catalog, and intent routing guidance.
|
|
4
|
+
*
|
|
5
|
+
* ## Tier 0 vs CLAUDE.md partition (DD-CE-002)
|
|
6
|
+
*
|
|
7
|
+
* When the chat engine runs on the `claude-code` runtime, the Claude Agent
|
|
8
|
+
* SDK loads project-level `CLAUDE.md` and user-level `~/.claude/CLAUDE.md`
|
|
9
|
+
* via `settingSources: ["user", "project"]`. To avoid double-prompting,
|
|
10
|
+
* this system prompt MUST stay scoped to:
|
|
11
|
+
*
|
|
12
|
+
* (a) Stagent identity
|
|
13
|
+
* (b) Stagent tool catalog and routing
|
|
14
|
+
* (c) Stagent domain semantics (delay steps, enrich_table, workflow dedup)
|
|
15
|
+
* (d) LLM interaction style
|
|
16
|
+
*
|
|
17
|
+
* Content that is project-specific (coding conventions, testing rules,
|
|
18
|
+
* git workflow, repo-specific gotchas) belongs in `CLAUDE.md` — NOT here.
|
|
19
|
+
*
|
|
20
|
+
* Audit (2026-04-13): every current block in this prompt passes the rubric.
|
|
21
|
+
* No content migration was required for Stagent's current CLAUDE.md state.
|
|
22
|
+
* The worktree note on line 110 is borderline and flagged for revisit if
|
|
23
|
+
* CLAUDE.md gains an explicit worktree section.
|
|
24
|
+
*
|
|
25
|
+
* Reference: features/chat-claude-sdk-skills.md (§"Tier 0 vs CLAUDE.md").
|
|
4
26
|
*/
|
|
5
27
|
|
|
6
28
|
export const STAGENT_SYSTEM_PROMPT = `You are Stagent, an AI workspace assistant for managing software projects, tasks, workflows, documents, and schedules. You are a full alternate UI for the Stagent app — users can do everything through chat that they can do in the GUI.
|
|
@@ -21,11 +43,12 @@ export const STAGENT_SYSTEM_PROMPT = `You are Stagent, an AI workspace assistant
|
|
|
21
43
|
|
|
22
44
|
### Workflows
|
|
23
45
|
- list_workflows: List all workflows
|
|
24
|
-
- create_workflow: Create a multi-step workflow with a definition
|
|
46
|
+
- create_workflow: Create a multi-step workflow with a definition. Steps can be task steps (profile + prompt) or **delay steps** (delayDuration like '3d', '2h', '30m', '1w') that pause the workflow before the next step. Delay steps enable time-distributed sequences.
|
|
25
47
|
- get_workflow: Get workflow details and definition
|
|
26
48
|
- update_workflow: Update a draft workflow
|
|
27
49
|
- delete_workflow: Delete a workflow and its children [requires approval]
|
|
28
50
|
- execute_workflow: Start workflow execution [requires approval]
|
|
51
|
+
- resume_workflow: Resume a paused (delayed) workflow immediately instead of waiting for its scheduled resume time [requires approval]
|
|
29
52
|
- get_workflow_status: Get current execution status with step progress
|
|
30
53
|
- find_related_documents: Search the project document pool for documents to attach as workflow context
|
|
31
54
|
|
|
@@ -58,9 +81,30 @@ export const STAGENT_SYSTEM_PROMPT = `You are Stagent, an AI workspace assistant
|
|
|
58
81
|
- get_usage_summary: Get token and cost statistics over a time period
|
|
59
82
|
- get_settings: Read current configuration (auth method, budgets, runtime)
|
|
60
83
|
|
|
84
|
+
### Tables
|
|
85
|
+
Structured user data lives in Stagent tables (separate from Stagent's own internal records). Every table tool takes a tableId; use list_tables or search_table to find them first.
|
|
86
|
+
- list_tables: List all user tables in a project
|
|
87
|
+
- get_table_schema: Get a table's columns, types, and metadata
|
|
88
|
+
- query_table: Filter, sort, and paginate rows with operators (eq, neq, gt, gte, lt, lte, contains, starts_with, in, is_empty, is_not_empty)
|
|
89
|
+
- search_table: Full-text search across row cell values
|
|
90
|
+
- aggregate_table: Compute count/sum/avg/min/max over a column with optional group-by
|
|
91
|
+
- add_rows: Insert one or more rows
|
|
92
|
+
- update_row: Update a single row's cell values
|
|
93
|
+
- delete_rows: Delete rows matching a filter [requires approval]
|
|
94
|
+
- create_table: Create a new empty table with specified columns
|
|
95
|
+
- import_document_as_table: Parse an uploaded document into a new table
|
|
96
|
+
- export_table: Export rows as CSV/JSON
|
|
97
|
+
- add_column / update_column / delete_column / reorder_columns: Schema edits
|
|
98
|
+
- list_triggers / create_trigger / update_trigger / delete_trigger: Per-row trigger evaluation
|
|
99
|
+
- get_table_history: Read change history for a table
|
|
100
|
+
- save_as_template: Save a table's shape as a reusable template
|
|
101
|
+
- **enrich_table**: Run an agent task for every row in a table matching a filter, writing results to a target column. Use for bulk research, classification, content generation, or any table-row fan-out pattern. Generates the optimal loop workflow, binds each row as context, skips already-populated rows for idempotency [requires approval]
|
|
102
|
+
|
|
61
103
|
## When to Use Which Tools
|
|
62
104
|
- CRUD operations ("create a task", "list workflows", "update the schedule") → Use the appropriate Stagent tool
|
|
63
105
|
- Execution ("run this task", "execute the workflow") → Use execute_task / execute_workflow
|
|
106
|
+
- Time-distributed multi-step sequences ("send email, wait 3 days, follow up", "drip campaign", "onboarding flow") → Use create_workflow with delay steps in a sequence pattern. Do NOT create separate workflows and schedules for each touch — a single workflow with inline delay steps is the idiomatic pattern.
|
|
107
|
+
- Bulk per-row operations ("research every contact", "classify all tickets", "enrich rows missing X", "for each row do Y") → Use enrich_table. Do NOT hand-roll a loop workflow for this — enrich_table already generates the optimal loop, handles row-data binding, wires up the postAction writeback, and skips already-populated rows for idempotency.
|
|
64
108
|
- Approvals ("approve that", "allow it", "deny the request") → Use respond_notification
|
|
65
109
|
- Monitoring ("what's pending?", "any approval requests?") → Use list_notifications
|
|
66
110
|
- Usage ("how much have I spent?", "token usage this week") → Use get_usage_summary
|
|
@@ -82,6 +126,9 @@ Be proactive with tools. If the user asks about project status, use list_tasks t
|
|
|
82
126
|
- If a project context is active, scope operations to it unless the user specifies otherwise.
|
|
83
127
|
- Tools marked [requires approval] will prompt the user before executing.
|
|
84
128
|
- For workflows, valid patterns are: sequence, parallel, checkpoint, planner-executor, swarm, loop.
|
|
129
|
+
- **Delay steps** (sequence pattern only): a step with \`delayDuration\` (format: Nm|Nh|Nd|Nw, bounds 1m..30d) pauses the workflow between task steps. Format examples: "30m", "2h", "3d", "1w". Delay steps must have NO profile or prompt — they are pure waits. Use them for outreach sequences, drip campaigns, cooling periods, staged rollouts. A paused workflow resumes automatically when its scheduled time arrives, or immediately when the user clicks "Resume Now".
|
|
130
|
+
- **enrich_table idempotency:** \`enrich_table\` skips rows where the target column already has a non-empty value. If the user wants to overwrite existing values, explain that force re-enrichment is not supported in v1 — they must manually clear the target column first (e.g. via update_row) before re-running.
|
|
131
|
+
- **create_workflow dedup:** Before calling \`create_workflow\`, call \`list_workflows\` (filtered by the current project) to check whether a similar workflow already exists. If the user asks to "redesign", "redo", or "update" an existing workflow, call \`update_workflow\` on the matching row instead of creating a new one. \`create_workflow\` performs its own near-duplicate check and will return \`{status: "similar-found", matches: [...]}\` instead of inserting when it finds one — when that happens, surface the matches to the user and confirm intent. Only pass \`force: true\` to \`create_workflow\` when the user has explicitly confirmed they want a second workflow alongside a similar one (e.g., "v2", "alternate approach").
|
|
85
132
|
- When a working directory is specified, always create files relative to it. Never assume the git root is the working directory — they may differ in worktree environments.
|
|
86
133
|
|
|
87
134
|
## Document Pool Awareness
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
Clock,
|
|
12
12
|
Globe,
|
|
13
13
|
Sun,
|
|
14
|
-
CheckCheck,
|
|
15
14
|
Sparkles,
|
|
16
15
|
Table2,
|
|
16
|
+
Zap,
|
|
17
17
|
} from "lucide-react";
|
|
18
18
|
import type { LucideIcon } from "lucide-react";
|
|
19
19
|
|
|
@@ -33,7 +33,8 @@ export type ToolGroup =
|
|
|
33
33
|
| "Settings"
|
|
34
34
|
| "Chat"
|
|
35
35
|
| "Browser"
|
|
36
|
-
| "Utility"
|
|
36
|
+
| "Utility"
|
|
37
|
+
| "Session";
|
|
37
38
|
|
|
38
39
|
export interface ToolCatalogEntry {
|
|
39
40
|
/** MCP tool name, e.g. "list_tasks" */
|
|
@@ -51,6 +52,7 @@ export interface ToolCatalogEntry {
|
|
|
51
52
|
// ── Group → Icon mapping ─────────────────────────────────────────────────
|
|
52
53
|
|
|
53
54
|
export const TOOL_GROUP_ICONS: Record<ToolGroup, LucideIcon> = {
|
|
55
|
+
Session: Zap,
|
|
54
56
|
Tasks: ListTodo,
|
|
55
57
|
Projects: FolderKanban,
|
|
56
58
|
Workflows: GitBranch,
|
|
@@ -69,6 +71,7 @@ export const TOOL_GROUP_ICONS: Record<ToolGroup, LucideIcon> = {
|
|
|
69
71
|
|
|
70
72
|
/** Display order for groups in the popover */
|
|
71
73
|
export const TOOL_GROUP_ORDER: ToolGroup[] = [
|
|
74
|
+
"Session",
|
|
72
75
|
"Tasks",
|
|
73
76
|
"Projects",
|
|
74
77
|
"Workflows",
|
|
@@ -110,6 +113,11 @@ const STAGENT_TOOLS: ToolCatalogEntry[] = [
|
|
|
110
113
|
{ name: "execute_workflow", description: "Start executing a workflow", group: "Workflows", paramHint: "workflowId" },
|
|
111
114
|
{ name: "delete_workflow", description: "Delete a workflow", group: "Workflows", paramHint: "workflowId" },
|
|
112
115
|
{ name: "get_workflow_status", description: "Get workflow execution progress", group: "Workflows", paramHint: "workflowId" },
|
|
116
|
+
{ name: "list_blueprints", description: "List available workflow blueprints", group: "Workflows", paramHint: "domain, search" },
|
|
117
|
+
{ name: "get_blueprint", description: "Get blueprint details and variables", group: "Workflows", paramHint: "blueprintId" },
|
|
118
|
+
{ name: "instantiate_blueprint", description: "Create a workflow from a blueprint", group: "Workflows", paramHint: "blueprintId, variables" },
|
|
119
|
+
{ name: "create_blueprint", description: "Create a custom workflow blueprint", group: "Workflows", paramHint: "yaml" },
|
|
120
|
+
{ name: "delete_blueprint", description: "Delete a custom blueprint", group: "Workflows", paramHint: "blueprintId" },
|
|
113
121
|
|
|
114
122
|
// ── Schedules ──
|
|
115
123
|
{ name: "list_schedules", description: "List scheduled prompt loops", group: "Schedules", paramHint: "status" },
|
|
@@ -134,6 +142,9 @@ const STAGENT_TOOLS: ToolCatalogEntry[] = [
|
|
|
134
142
|
// ── Profiles ──
|
|
135
143
|
{ name: "list_profiles", description: "List available agent profiles", group: "Profiles" },
|
|
136
144
|
{ name: "get_profile", description: "Get agent profile configuration", group: "Profiles", paramHint: "profileId" },
|
|
145
|
+
{ name: "create_profile", description: "Create a new agent profile", group: "Profiles", paramHint: "config, skillMd" },
|
|
146
|
+
{ name: "update_profile", description: "Update a custom agent profile", group: "Profiles", paramHint: "profileId, config, skillMd" },
|
|
147
|
+
{ name: "delete_profile", description: "Delete a custom agent profile", group: "Profiles", paramHint: "profileId" },
|
|
137
148
|
|
|
138
149
|
// ── Usage ──
|
|
139
150
|
{ name: "get_usage_summary", description: "Get spending and token usage stats", group: "Usage", paramHint: "days" },
|
|
@@ -142,6 +153,12 @@ const STAGENT_TOOLS: ToolCatalogEntry[] = [
|
|
|
142
153
|
{ name: "get_settings", description: "Get current Stagent settings", group: "Settings", paramHint: "key" },
|
|
143
154
|
{ name: "set_settings", description: "Update a Stagent setting (approval required)", group: "Settings", paramHint: "key, value" },
|
|
144
155
|
|
|
156
|
+
// ── Skills ──
|
|
157
|
+
{ name: "list_skills", description: "List all discoverable skills (user + project scopes)", group: "Skills" },
|
|
158
|
+
{ name: "get_skill", description: "Get full SKILL.md content + metadata for one skill", group: "Skills", paramHint: "id" },
|
|
159
|
+
{ name: "activate_skill", description: "Bind a skill to a conversation — SKILL.md is injected into every turn's system prompt. Pass mode='add' to compose (runtime-gated).", group: "Skills", paramHint: "conversationId, skillId, mode?" },
|
|
160
|
+
{ name: "deactivate_skill", description: "Clear the active skill from a conversation", group: "Skills", paramHint: "conversationId" },
|
|
161
|
+
|
|
145
162
|
// ── Tables ──
|
|
146
163
|
{ name: "list_tables", description: "List tables, filter by project or source", group: "Tables", paramHint: "projectId, source" },
|
|
147
164
|
{ name: "get_table_schema", description: "Get column definitions for a table", group: "Tables", paramHint: "tableId" },
|
|
@@ -171,11 +188,13 @@ const STAGENT_TOOLS: ToolCatalogEntry[] = [
|
|
|
171
188
|
{ name: "delete_trigger", description: "Delete a trigger", group: "Tables", paramHint: "tableId, triggerId" },
|
|
172
189
|
{ name: "get_table_history", description: "Get row change history for a table", group: "Tables", paramHint: "tableId, limit" },
|
|
173
190
|
{ name: "save_as_template", description: "Save a table as a reusable template", group: "Tables", paramHint: "tableId, name, category" },
|
|
191
|
+
{ name: "enrich_table", description: "Bulk-enrich rows by running an agent task per row, writing results to a target column", group: "Tables", paramHint: "tableId, prompt, targetColumn, filter" },
|
|
174
192
|
|
|
175
193
|
// ── Chat History ──
|
|
176
194
|
{ name: "list_conversations", description: "List recent chat conversations", group: "Chat", paramHint: "search, limit" },
|
|
177
195
|
{ name: "get_conversation_messages", description: "Get messages from a past conversation", group: "Chat", paramHint: "conversationId, limit" },
|
|
178
196
|
{ name: "search_messages", description: "Search across all conversations", group: "Chat", paramHint: "query" },
|
|
197
|
+
|
|
179
198
|
];
|
|
180
199
|
|
|
181
200
|
const BROWSER_TOOLS: ToolCatalogEntry[] = [
|
|
@@ -187,6 +206,18 @@ const BROWSER_TOOLS: ToolCatalogEntry[] = [
|
|
|
187
206
|
{ name: "take_snapshot", description: "Take an accessibility snapshot", group: "Browser" },
|
|
188
207
|
];
|
|
189
208
|
|
|
209
|
+
const SESSION_ENTRIES: ToolCatalogEntry[] = [
|
|
210
|
+
{ name: "clear", description: "Start a new conversation", group: "Session", behavior: "execute_immediately" },
|
|
211
|
+
{ name: "compact", description: "Summarize and compact conversation history", group: "Session", behavior: "execute_immediately" },
|
|
212
|
+
{ name: "export", description: "Save current conversation as a document", group: "Session", behavior: "execute_immediately" },
|
|
213
|
+
{ name: "help", description: "Show chat shortcuts and commands", group: "Session", behavior: "execute_immediately" },
|
|
214
|
+
{ name: "settings", description: "Open Stagent settings", group: "Session", behavior: "execute_immediately" },
|
|
215
|
+
{ name: "new-task", description: "Create a new task", group: "Session", paramHint: "title" },
|
|
216
|
+
{ name: "new-workflow", description: "Create a new workflow", group: "Session", paramHint: "name" },
|
|
217
|
+
{ name: "new-schedule", description: "Create a new schedule", group: "Session", paramHint: "name, interval" },
|
|
218
|
+
{ name: "new-from-template", description: "Start a conversation from a workflow blueprint", group: "Session", behavior: "execute_immediately" },
|
|
219
|
+
];
|
|
220
|
+
|
|
190
221
|
const UTILITY_ENTRIES: ToolCatalogEntry[] = [
|
|
191
222
|
{ name: "toggle_theme", description: "Switch dark/light mode", group: "Utility", behavior: "execute_immediately" },
|
|
192
223
|
{ name: "mark_all_read", description: "Mark all notifications as read", group: "Utility", behavior: "execute_immediately" },
|
|
@@ -202,13 +233,13 @@ export function getToolCatalog(opts?: { includeBrowser?: boolean }): ToolCatalog
|
|
|
202
233
|
|
|
203
234
|
if (withBrowser) {
|
|
204
235
|
if (!cachedWithBrowser) {
|
|
205
|
-
cachedWithBrowser = [...STAGENT_TOOLS, ...BROWSER_TOOLS, ...UTILITY_ENTRIES];
|
|
236
|
+
cachedWithBrowser = [...SESSION_ENTRIES, ...STAGENT_TOOLS, ...BROWSER_TOOLS, ...UTILITY_ENTRIES];
|
|
206
237
|
}
|
|
207
238
|
return cachedWithBrowser;
|
|
208
239
|
}
|
|
209
240
|
|
|
210
241
|
if (!cachedCatalog) {
|
|
211
|
-
cachedCatalog = [...STAGENT_TOOLS, ...UTILITY_ENTRIES];
|
|
242
|
+
cachedCatalog = [...SESSION_ENTRIES, ...STAGENT_TOOLS, ...UTILITY_ENTRIES];
|
|
212
243
|
}
|
|
213
244
|
return cachedCatalog;
|
|
214
245
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
const { mockCreateEnrichmentWorkflow } = vi.hoisted(() => ({
|
|
4
|
+
mockCreateEnrichmentWorkflow: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock("@/lib/tables/enrichment", () => ({
|
|
8
|
+
createEnrichmentWorkflow: mockCreateEnrichmentWorkflow,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
// Stub the rest of @/lib/data/tables so importing table-tools doesn't drag in DB.
|
|
12
|
+
vi.mock("@/lib/data/tables", () => ({
|
|
13
|
+
listTables: vi.fn(),
|
|
14
|
+
getTable: vi.fn(),
|
|
15
|
+
createTable: vi.fn(),
|
|
16
|
+
updateTable: vi.fn(),
|
|
17
|
+
deleteTable: vi.fn(),
|
|
18
|
+
listRows: vi.fn(),
|
|
19
|
+
addRows: vi.fn(),
|
|
20
|
+
updateRow: vi.fn(),
|
|
21
|
+
deleteRows: vi.fn(),
|
|
22
|
+
listTemplates: vi.fn(),
|
|
23
|
+
cloneFromTemplate: vi.fn(),
|
|
24
|
+
addColumn: vi.fn(),
|
|
25
|
+
updateColumn: vi.fn(),
|
|
26
|
+
deleteColumn: vi.fn(),
|
|
27
|
+
reorderColumns: vi.fn(),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
vi.mock("@/lib/tables/history", () => ({ getTableHistory: vi.fn() }));
|
|
31
|
+
vi.mock("@/lib/tables/import", () => ({
|
|
32
|
+
extractStructuredData: vi.fn(),
|
|
33
|
+
inferColumnTypes: vi.fn(),
|
|
34
|
+
importRows: vi.fn(),
|
|
35
|
+
createImportRecord: vi.fn(),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
import { tableTools } from "../table-tools";
|
|
39
|
+
|
|
40
|
+
function findEnrichTool() {
|
|
41
|
+
const tools = tableTools({ projectId: "proj_test" });
|
|
42
|
+
const tool = tools.find((t) => t.name === "enrich_table");
|
|
43
|
+
if (!tool) throw new Error("enrich_table tool not registered");
|
|
44
|
+
return tool;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("enrich_table tool", () => {
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
mockCreateEnrichmentWorkflow.mockReset();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("is registered in tableTools", () => {
|
|
53
|
+
const tools = tableTools({ projectId: "proj_test" });
|
|
54
|
+
const names = tools.map((t) => t.name);
|
|
55
|
+
expect(names).toContain("enrich_table");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("delegates to createEnrichmentWorkflow with the supplied params", async () => {
|
|
59
|
+
mockCreateEnrichmentWorkflow.mockResolvedValueOnce({
|
|
60
|
+
workflowId: "wf_xyz",
|
|
61
|
+
rowCount: 4,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const tool = findEnrichTool();
|
|
65
|
+
const result = await tool.handler({
|
|
66
|
+
tableId: "tbl_contacts",
|
|
67
|
+
prompt: "Find LinkedIn for {{row.name}}",
|
|
68
|
+
targetColumn: "linkedin",
|
|
69
|
+
filter: { column: "linkedin", operator: "is_empty" },
|
|
70
|
+
agentProfile: "sales-researcher",
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(mockCreateEnrichmentWorkflow).toHaveBeenCalledWith(
|
|
74
|
+
"tbl_contacts",
|
|
75
|
+
expect.objectContaining({
|
|
76
|
+
prompt: "Find LinkedIn for {{row.name}}",
|
|
77
|
+
targetColumn: "linkedin",
|
|
78
|
+
filter: { column: "linkedin", operator: "is_empty" },
|
|
79
|
+
agentProfile: "sales-researcher",
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(result.isError).toBeFalsy();
|
|
84
|
+
const payload = JSON.parse(result.content[0].text) as {
|
|
85
|
+
workflowId: string;
|
|
86
|
+
rowCount: number;
|
|
87
|
+
};
|
|
88
|
+
expect(payload.workflowId).toBe("wf_xyz");
|
|
89
|
+
expect(payload.rowCount).toBe(4);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("falls back to ctx.projectId when projectId is not supplied", async () => {
|
|
93
|
+
mockCreateEnrichmentWorkflow.mockResolvedValueOnce({
|
|
94
|
+
workflowId: "wf_a",
|
|
95
|
+
rowCount: 1,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const tool = findEnrichTool();
|
|
99
|
+
await tool.handler({
|
|
100
|
+
tableId: "tbl_x",
|
|
101
|
+
prompt: "x",
|
|
102
|
+
targetColumn: "linkedin",
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const callArg = mockCreateEnrichmentWorkflow.mock.calls[0][1] as {
|
|
106
|
+
projectId?: string;
|
|
107
|
+
};
|
|
108
|
+
expect(callArg.projectId).toBe("proj_test");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("returns an error result when createEnrichmentWorkflow throws", async () => {
|
|
112
|
+
mockCreateEnrichmentWorkflow.mockRejectedValueOnce(
|
|
113
|
+
new Error("Table tbl_missing not found")
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const tool = findEnrichTool();
|
|
117
|
+
const result = await tool.handler({
|
|
118
|
+
tableId: "tbl_missing",
|
|
119
|
+
prompt: "x",
|
|
120
|
+
targetColumn: "linkedin",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(result.isError).toBe(true);
|
|
124
|
+
const payload = JSON.parse(result.content[0].text) as { error: string };
|
|
125
|
+
expect(payload.error).toContain("not found");
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("@/lib/agents/profiles/list-fused-profiles", () => ({
|
|
4
|
+
listFusedProfiles: vi.fn(async (projectDir: string | null) =>
|
|
5
|
+
[
|
|
6
|
+
{
|
|
7
|
+
id: "general",
|
|
8
|
+
name: "General",
|
|
9
|
+
description: "Reg",
|
|
10
|
+
domain: "general",
|
|
11
|
+
tags: [],
|
|
12
|
+
},
|
|
13
|
+
projectDir
|
|
14
|
+
? {
|
|
15
|
+
id: "project-only",
|
|
16
|
+
name: "Project Only",
|
|
17
|
+
description: "Proj",
|
|
18
|
+
domain: "skill",
|
|
19
|
+
tags: [],
|
|
20
|
+
origin: "filesystem-project",
|
|
21
|
+
}
|
|
22
|
+
: null,
|
|
23
|
+
].filter(Boolean)
|
|
24
|
+
),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
describe("list_profiles chat tool", () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
vi.clearAllMocks();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("returns fused profiles when called with a projectDir", async () => {
|
|
33
|
+
const { getListProfilesTool } = await import("@/lib/chat/tools/profile-tools");
|
|
34
|
+
const tool = getListProfilesTool("/fake/project");
|
|
35
|
+
const result = await tool.handler({});
|
|
36
|
+
// ok() wraps data as MCP content — parse the JSON text back out
|
|
37
|
+
const text = result.content[0].text;
|
|
38
|
+
const list = JSON.parse(text) as { id: string }[];
|
|
39
|
+
expect(Array.isArray(list)).toBe(true);
|
|
40
|
+
expect(list.some((p) => p.id === "project-only")).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("returns registry-only profiles when projectDir is null", async () => {
|
|
44
|
+
const { getListProfilesTool } = await import("@/lib/chat/tools/profile-tools");
|
|
45
|
+
const tool = getListProfilesTool(null);
|
|
46
|
+
const result = await tool.handler({});
|
|
47
|
+
const text = result.content[0].text;
|
|
48
|
+
const list = JSON.parse(text) as { id: string }[];
|
|
49
|
+
expect(list.every((p) => p.id !== "project-only")).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
interface ScheduleRow {
|
|
5
|
+
id: string;
|
|
6
|
+
maxTurns: number | null;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { mockState } = vi.hoisted(() => ({
|
|
11
|
+
mockState: {
|
|
12
|
+
rows: [] as ScheduleRow[],
|
|
13
|
+
lastInsertValues: null as Record<string, unknown> | null,
|
|
14
|
+
lastUpdateValues: null as Record<string, unknown> | null,
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
vi.mock("@/lib/db", () => {
|
|
19
|
+
const selectBuilder = {
|
|
20
|
+
from() { return this; },
|
|
21
|
+
where() { return this; },
|
|
22
|
+
orderBy() { return this; },
|
|
23
|
+
limit() { return this; },
|
|
24
|
+
get() { return Promise.resolve(mockState.rows[0]); },
|
|
25
|
+
then<TResolve>(resolve: (rows: ScheduleRow[]) => TResolve) {
|
|
26
|
+
return Promise.resolve(mockState.rows).then(resolve);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
db: {
|
|
31
|
+
select: () => selectBuilder,
|
|
32
|
+
insert: () => ({
|
|
33
|
+
values: (v: Record<string, unknown>) => {
|
|
34
|
+
mockState.lastInsertValues = v;
|
|
35
|
+
mockState.rows = [{ id: "sched-1", maxTurns: null, ...v } as ScheduleRow];
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
update: () => ({
|
|
40
|
+
set: (v: Record<string, unknown>) => {
|
|
41
|
+
mockState.lastUpdateValues = v;
|
|
42
|
+
mockState.rows[0] = { ...mockState.rows[0], ...v } as ScheduleRow;
|
|
43
|
+
return { where: () => Promise.resolve() };
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
delete: () => ({ where: () => Promise.resolve() }),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
vi.mock("@/lib/db/schema", () => ({
|
|
52
|
+
schedules: {
|
|
53
|
+
id: "id",
|
|
54
|
+
status: "status",
|
|
55
|
+
projectId: "projectId",
|
|
56
|
+
updatedAt: "updatedAt",
|
|
57
|
+
cronExpression: "cronExpression",
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
vi.mock("drizzle-orm", () => ({
|
|
62
|
+
eq: () => ({}),
|
|
63
|
+
and: () => ({}),
|
|
64
|
+
desc: () => ({}),
|
|
65
|
+
like: () => ({}),
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
vi.mock("@/lib/schedules/interval-parser", () => ({
|
|
69
|
+
parseInterval: () => "*/30 * * * *",
|
|
70
|
+
computeNextFireTime: () => new Date("2026-04-11T10:00:00Z"),
|
|
71
|
+
computeStaggeredCron: (cron: string) => ({
|
|
72
|
+
cronExpression: cron,
|
|
73
|
+
offsetApplied: 0,
|
|
74
|
+
collided: false,
|
|
75
|
+
}),
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
vi.mock("@/lib/schedules/nlp-parser", () => ({
|
|
79
|
+
parseNaturalLanguage: () => null,
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
vi.mock("@/lib/schedules/prompt-analyzer", () => ({
|
|
83
|
+
analyzePromptEfficiency: () => [],
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
import { scheduleTools } from "../schedule-tools";
|
|
87
|
+
|
|
88
|
+
function getTool(name: string) {
|
|
89
|
+
const tools = scheduleTools({ projectId: "proj-1" } as never);
|
|
90
|
+
const tool = tools.find((t) => t.name === name);
|
|
91
|
+
if (!tool) throw new Error(`Tool not found: ${name}`);
|
|
92
|
+
return tool;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseArgs(toolName: string, args: unknown) {
|
|
96
|
+
const tool = getTool(toolName);
|
|
97
|
+
return z.object(tool.zodShape).safeParse(args);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
beforeEach(() => {
|
|
101
|
+
mockState.rows = [];
|
|
102
|
+
mockState.lastInsertValues = null;
|
|
103
|
+
mockState.lastUpdateValues = null;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("create_schedule maxTurns Zod validation", () => {
|
|
107
|
+
const base = {
|
|
108
|
+
name: "test",
|
|
109
|
+
prompt: "hello",
|
|
110
|
+
interval: "every 30 minutes",
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
it("accepts a valid maxTurns value", () => {
|
|
114
|
+
const result = parseArgs("create_schedule", { ...base, maxTurns: 50 });
|
|
115
|
+
expect(result.success).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("accepts omitted maxTurns (inherit default)", () => {
|
|
119
|
+
const result = parseArgs("create_schedule", base);
|
|
120
|
+
expect(result.success).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("rejects maxTurns below 10", () => {
|
|
124
|
+
const result = parseArgs("create_schedule", { ...base, maxTurns: 9 });
|
|
125
|
+
expect(result.success).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("rejects maxTurns above 500", () => {
|
|
129
|
+
const result = parseArgs("create_schedule", { ...base, maxTurns: 501 });
|
|
130
|
+
expect(result.success).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("rejects non-integer maxTurns", () => {
|
|
134
|
+
const result = parseArgs("create_schedule", { ...base, maxTurns: 50.5 });
|
|
135
|
+
expect(result.success).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("rejects explicit null on create (only update supports clear-to-null)", () => {
|
|
139
|
+
const result = parseArgs("create_schedule", { ...base, maxTurns: null });
|
|
140
|
+
expect(result.success).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("update_schedule maxTurns Zod validation", () => {
|
|
145
|
+
const base = { scheduleId: "sched-1" };
|
|
146
|
+
|
|
147
|
+
it("accepts a valid maxTurns value", () => {
|
|
148
|
+
const result = parseArgs("update_schedule", { ...base, maxTurns: 100 });
|
|
149
|
+
expect(result.success).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("accepts explicit null to clear an override", () => {
|
|
153
|
+
const result = parseArgs("update_schedule", { ...base, maxTurns: null });
|
|
154
|
+
expect(result.success).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("accepts omitted maxTurns (unchanged)", () => {
|
|
158
|
+
const result = parseArgs("update_schedule", base);
|
|
159
|
+
expect(result.success).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("rejects out-of-range maxTurns on update", () => {
|
|
163
|
+
const result = parseArgs("update_schedule", { ...base, maxTurns: 9 });
|
|
164
|
+
expect(result.success).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("create_schedule maxTurns persistence", () => {
|
|
169
|
+
it("writes maxTurns to the insert payload when provided", async () => {
|
|
170
|
+
const tool = getTool("create_schedule");
|
|
171
|
+
await tool.handler({
|
|
172
|
+
name: "test",
|
|
173
|
+
prompt: "hello",
|
|
174
|
+
interval: "every 30 minutes",
|
|
175
|
+
maxTurns: 75,
|
|
176
|
+
});
|
|
177
|
+
expect(mockState.lastInsertValues).not.toBeNull();
|
|
178
|
+
expect(mockState.lastInsertValues?.maxTurns).toBe(75);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("writes null to maxTurns when omitted (inherit default)", async () => {
|
|
182
|
+
const tool = getTool("create_schedule");
|
|
183
|
+
await tool.handler({
|
|
184
|
+
name: "test",
|
|
185
|
+
prompt: "hello",
|
|
186
|
+
interval: "every 30 minutes",
|
|
187
|
+
});
|
|
188
|
+
expect(mockState.lastInsertValues?.maxTurns).toBe(null);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("sets maxTurnsSetAt to a Date when maxTurns is provided", async () => {
|
|
192
|
+
const tool = getTool("create_schedule");
|
|
193
|
+
await tool.handler({
|
|
194
|
+
name: "test",
|
|
195
|
+
prompt: "hello",
|
|
196
|
+
interval: "every 30 minutes",
|
|
197
|
+
maxTurns: 75,
|
|
198
|
+
});
|
|
199
|
+
expect(mockState.lastInsertValues?.maxTurnsSetAt).toBeInstanceOf(Date);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("sets maxTurnsSetAt to null when maxTurns is omitted", async () => {
|
|
203
|
+
const tool = getTool("create_schedule");
|
|
204
|
+
await tool.handler({
|
|
205
|
+
name: "test",
|
|
206
|
+
prompt: "hello",
|
|
207
|
+
interval: "every 30 minutes",
|
|
208
|
+
});
|
|
209
|
+
expect(mockState.lastInsertValues?.maxTurnsSetAt).toBe(null);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("update_schedule maxTurns persistence", () => {
|
|
214
|
+
beforeEach(() => {
|
|
215
|
+
mockState.rows = [{
|
|
216
|
+
id: "sched-1",
|
|
217
|
+
name: "existing",
|
|
218
|
+
status: "active",
|
|
219
|
+
maxTurns: 50,
|
|
220
|
+
} as ScheduleRow];
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("writes the new maxTurns value when provided", async () => {
|
|
224
|
+
const tool = getTool("update_schedule");
|
|
225
|
+
await tool.handler({ scheduleId: "sched-1", maxTurns: 120 });
|
|
226
|
+
expect(mockState.lastUpdateValues?.maxTurns).toBe(120);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("writes null when explicitly clearing the override", async () => {
|
|
230
|
+
const tool = getTool("update_schedule");
|
|
231
|
+
await tool.handler({ scheduleId: "sched-1", maxTurns: null });
|
|
232
|
+
expect(mockState.lastUpdateValues).not.toBeNull();
|
|
233
|
+
expect("maxTurns" in (mockState.lastUpdateValues ?? {})).toBe(true);
|
|
234
|
+
expect(mockState.lastUpdateValues?.maxTurns).toBe(null);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("does not touch maxTurns when the field is omitted", async () => {
|
|
238
|
+
const tool = getTool("update_schedule");
|
|
239
|
+
await tool.handler({ scheduleId: "sched-1", name: "renamed" });
|
|
240
|
+
expect("maxTurns" in (mockState.lastUpdateValues ?? {})).toBe(false);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("sets maxTurnsSetAt to a Date when maxTurns is set to a number", async () => {
|
|
244
|
+
const tool = getTool("update_schedule");
|
|
245
|
+
await tool.handler({ scheduleId: "sched-1", maxTurns: 120 });
|
|
246
|
+
expect(mockState.lastUpdateValues?.maxTurnsSetAt).toBeInstanceOf(Date);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("sets maxTurnsSetAt to null when maxTurns is cleared", async () => {
|
|
250
|
+
const tool = getTool("update_schedule");
|
|
251
|
+
await tool.handler({ scheduleId: "sched-1", maxTurns: null });
|
|
252
|
+
expect("maxTurnsSetAt" in (mockState.lastUpdateValues ?? {})).toBe(true);
|
|
253
|
+
expect(mockState.lastUpdateValues?.maxTurnsSetAt).toBe(null);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("does not touch maxTurnsSetAt when maxTurns is omitted", async () => {
|
|
257
|
+
const tool = getTool("update_schedule");
|
|
258
|
+
await tool.handler({ scheduleId: "sched-1", name: "renamed" });
|
|
259
|
+
expect("maxTurnsSetAt" in (mockState.lastUpdateValues ?? {})).toBe(false);
|
|
260
|
+
});
|
|
261
|
+
});
|