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
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
* Shared helpers and types for Stagent chat MCP tools.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { db } from "@/lib/db";
|
|
6
|
+
import { like } from "drizzle-orm";
|
|
7
|
+
import type { SQLiteTableWithColumns } from "drizzle-orm/sqlite-core";
|
|
8
|
+
import type { SQLiteColumn } from "drizzle-orm/sqlite-core";
|
|
9
|
+
|
|
5
10
|
/** Context passed to each tool factory — provides project scoping and entity callbacks. */
|
|
6
11
|
export interface ToolContext {
|
|
7
12
|
projectId?: string | null;
|
|
13
|
+
/** Absolute path to the active project's working directory. Used by profile tools to surface filesystem skills. */
|
|
14
|
+
projectDir?: string | null;
|
|
8
15
|
onToolResult?: (toolName: string, result: unknown) => void;
|
|
9
16
|
}
|
|
10
17
|
|
|
@@ -22,3 +29,37 @@ export function err(message: string) {
|
|
|
22
29
|
isError: true as const,
|
|
23
30
|
};
|
|
24
31
|
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolve an entity ID that may be a prefix (8+ chars) to the full UUID.
|
|
35
|
+
* Uses LIKE 'prefix%' which hits the primary key B-tree index on SQLite.
|
|
36
|
+
*
|
|
37
|
+
* Fast path: IDs >=32 chars are returned as-is (already full UUIDs).
|
|
38
|
+
*/
|
|
39
|
+
export async function resolveEntityId(
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
+
table: SQLiteTableWithColumns<any>,
|
|
42
|
+
idColumn: SQLiteColumn,
|
|
43
|
+
rawId: string,
|
|
44
|
+
): Promise<{ id: string } | { error: string }> {
|
|
45
|
+
// Full UUIDs are 36 chars (with hyphens) or 32 (without) — skip prefix search
|
|
46
|
+
if (rawId.length >= 32) {
|
|
47
|
+
return { id: rawId };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const matches = await db
|
|
51
|
+
.select({ id: idColumn })
|
|
52
|
+
.from(table)
|
|
53
|
+
.where(like(idColumn, `${rawId}%`))
|
|
54
|
+
.limit(2);
|
|
55
|
+
|
|
56
|
+
if (matches.length === 0) {
|
|
57
|
+
return { error: `No entity found matching ID prefix: ${rawId}` };
|
|
58
|
+
}
|
|
59
|
+
if (matches.length > 1) {
|
|
60
|
+
return {
|
|
61
|
+
error: `Ambiguous ID prefix "${rawId}" matches multiple entities: ${matches.map((m) => m.id).join(", ")}. Use the full ID.`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { id: matches[0].id as string };
|
|
65
|
+
}
|
|
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { db } from "@/lib/db";
|
|
4
4
|
import { notifications } from "@/lib/db/schema";
|
|
5
5
|
import { eq, isNull, desc } from "drizzle-orm";
|
|
6
|
-
import { ok, err, type ToolContext } from "./helpers";
|
|
6
|
+
import { ok, err, resolveEntityId, type ToolContext } from "./helpers";
|
|
7
7
|
|
|
8
8
|
export function notificationTools(_ctx: ToolContext) {
|
|
9
9
|
return [
|
|
@@ -73,13 +73,17 @@ export function notificationTools(_ctx: ToolContext) {
|
|
|
73
73
|
},
|
|
74
74
|
async (args) => {
|
|
75
75
|
try {
|
|
76
|
+
const resolved = await resolveEntityId(notifications, notifications.id, args.notificationId);
|
|
77
|
+
if ("error" in resolved) return err(resolved.error);
|
|
78
|
+
const notificationId = resolved.id;
|
|
79
|
+
|
|
76
80
|
const notification = await db
|
|
77
81
|
.select()
|
|
78
82
|
.from(notifications)
|
|
79
|
-
.where(eq(notifications.id,
|
|
83
|
+
.where(eq(notifications.id, notificationId))
|
|
80
84
|
.get();
|
|
81
85
|
|
|
82
|
-
if (!notification) return err(`Notification not found: ${
|
|
86
|
+
if (!notification) return err(`Notification not found: ${notificationId}`);
|
|
83
87
|
if (notification.response) return err("Already responded to this notification");
|
|
84
88
|
|
|
85
89
|
const responseData = {
|
|
@@ -98,7 +102,7 @@ export function notificationTools(_ctx: ToolContext) {
|
|
|
98
102
|
respondedAt: new Date(),
|
|
99
103
|
read: true,
|
|
100
104
|
})
|
|
101
|
-
.where(eq(notifications.id,
|
|
105
|
+
.where(eq(notifications.id, notificationId));
|
|
102
106
|
|
|
103
107
|
// Save permanent permission if requested
|
|
104
108
|
if (args.behavior === "allow" && args.alwaysAllow && notification.toolName && notification.toolInput) {
|
|
@@ -115,7 +119,7 @@ export function notificationTools(_ctx: ToolContext) {
|
|
|
115
119
|
|
|
116
120
|
return ok({
|
|
117
121
|
message: `Notification ${args.behavior === "allow" ? "approved" : "denied"}`,
|
|
118
|
-
notificationId
|
|
122
|
+
notificationId,
|
|
119
123
|
alwaysAllow: args.alwaysAllow ?? false,
|
|
120
124
|
});
|
|
121
125
|
} catch (e) {
|
|
@@ -2,30 +2,42 @@ import { defineTool } from "../tool-registry";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { ok, err, type ToolContext } from "./helpers";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Factory for the list_profiles tool, parameterized by projectDir so it can
|
|
7
|
+
* surface project filesystem skills alongside registry profiles via
|
|
8
|
+
* listFusedProfiles. See features/chat-claude-sdk-skills.md.
|
|
9
|
+
*/
|
|
10
|
+
export function getListProfilesTool(projectDir: string | null) {
|
|
11
|
+
return defineTool(
|
|
12
|
+
"list_profiles",
|
|
13
|
+
"List all available agent profiles and filesystem skills with their capabilities and compatible runtimes.",
|
|
14
|
+
{},
|
|
15
|
+
async () => {
|
|
16
|
+
try {
|
|
17
|
+
const { listFusedProfiles } = await import(
|
|
18
|
+
"@/lib/agents/profiles/list-fused-profiles"
|
|
19
|
+
);
|
|
20
|
+
const profiles = await listFusedProfiles(projectDir);
|
|
21
|
+
return ok(
|
|
22
|
+
profiles.map((p) => ({
|
|
23
|
+
id: p.id,
|
|
24
|
+
name: p.name,
|
|
25
|
+
description: p.description,
|
|
26
|
+
domain: p.domain,
|
|
27
|
+
tags: p.tags,
|
|
28
|
+
origin: p.origin ?? "registry",
|
|
29
|
+
}))
|
|
30
|
+
);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return err(e instanceof Error ? e.message : "Failed to list profiles");
|
|
27
33
|
}
|
|
28
|
-
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function profileTools(ctx: ToolContext) {
|
|
39
|
+
return [
|
|
40
|
+
getListProfilesTool(ctx.projectDir ?? null),
|
|
29
41
|
|
|
30
42
|
defineTool(
|
|
31
43
|
"get_profile",
|
|
@@ -44,5 +56,90 @@ export function profileTools(_ctx: ToolContext) {
|
|
|
44
56
|
}
|
|
45
57
|
}
|
|
46
58
|
),
|
|
59
|
+
|
|
60
|
+
defineTool(
|
|
61
|
+
"create_profile",
|
|
62
|
+
"Create a new agent profile with a configuration and system prompt (SKILL.md). The profile is saved to ~/.claude/skills/ and becomes immediately available. Use get_profile on an existing profile to see the expected config structure.",
|
|
63
|
+
{
|
|
64
|
+
config: z.object({
|
|
65
|
+
id: z.string().min(1).describe("Unique profile ID (kebab-case, e.g. 'my-analyst')"),
|
|
66
|
+
name: z.string().min(1).describe("Human-readable profile name"),
|
|
67
|
+
version: z.string().regex(/^\d+\.\d+\.\d+$/).describe("Semver version, e.g. '1.0.0'"),
|
|
68
|
+
domain: z.enum(["work", "personal"]).describe("Profile domain"),
|
|
69
|
+
tags: z.array(z.string()).describe("Searchable tags"),
|
|
70
|
+
maxTurns: z.number().positive().optional().describe("Max agent turns per task"),
|
|
71
|
+
outputFormat: z.string().optional().describe("Expected output format hint"),
|
|
72
|
+
author: z.string().optional().describe("Profile author"),
|
|
73
|
+
}).describe("Profile configuration object"),
|
|
74
|
+
skillMd: z.string().min(1).describe(
|
|
75
|
+
"The SKILL.md content — this is the system prompt that defines the agent's behavior, personality, and instructions. Markdown format."
|
|
76
|
+
),
|
|
77
|
+
},
|
|
78
|
+
async (args) => {
|
|
79
|
+
try {
|
|
80
|
+
const { createProfile } = await import("@/lib/agents/profiles/registry");
|
|
81
|
+
createProfile(args.config, args.skillMd);
|
|
82
|
+
ctx.onToolResult?.("create_profile", { id: args.config.id, name: args.config.name });
|
|
83
|
+
return ok({
|
|
84
|
+
id: args.config.id,
|
|
85
|
+
name: args.config.name,
|
|
86
|
+
message: "Profile created successfully",
|
|
87
|
+
});
|
|
88
|
+
} catch (e) {
|
|
89
|
+
return err(e instanceof Error ? e.message : "Failed to create profile");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
),
|
|
93
|
+
|
|
94
|
+
defineTool(
|
|
95
|
+
"update_profile",
|
|
96
|
+
"Update an existing agent profile's configuration and/or system prompt. Built-in profiles cannot be modified — duplicate them first with create_profile.",
|
|
97
|
+
{
|
|
98
|
+
profileId: z.string().describe("The profile ID to update"),
|
|
99
|
+
config: z.object({
|
|
100
|
+
id: z.string().min(1),
|
|
101
|
+
name: z.string().min(1),
|
|
102
|
+
version: z.string().regex(/^\d+\.\d+\.\d+$/),
|
|
103
|
+
domain: z.enum(["work", "personal"]),
|
|
104
|
+
tags: z.array(z.string()),
|
|
105
|
+
maxTurns: z.number().positive().optional(),
|
|
106
|
+
outputFormat: z.string().optional(),
|
|
107
|
+
author: z.string().optional(),
|
|
108
|
+
}).describe("Full profile configuration (replaces existing)"),
|
|
109
|
+
skillMd: z.string().min(1).describe("Updated SKILL.md content"),
|
|
110
|
+
},
|
|
111
|
+
async (args) => {
|
|
112
|
+
try {
|
|
113
|
+
const { updateProfile } = await import("@/lib/agents/profiles/registry");
|
|
114
|
+
updateProfile(args.profileId, args.config, args.skillMd);
|
|
115
|
+
ctx.onToolResult?.("update_profile", { id: args.profileId });
|
|
116
|
+
return ok({
|
|
117
|
+
id: args.profileId,
|
|
118
|
+
message: "Profile updated successfully",
|
|
119
|
+
});
|
|
120
|
+
} catch (e) {
|
|
121
|
+
return err(e instanceof Error ? e.message : "Failed to update profile");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
|
|
126
|
+
defineTool(
|
|
127
|
+
"delete_profile",
|
|
128
|
+
"Delete a custom agent profile. Built-in profiles cannot be deleted.",
|
|
129
|
+
{
|
|
130
|
+
profileId: z.string().describe("The profile ID to delete"),
|
|
131
|
+
},
|
|
132
|
+
async (args) => {
|
|
133
|
+
try {
|
|
134
|
+
const { deleteProfile } = await import("@/lib/agents/profiles/registry");
|
|
135
|
+
deleteProfile(args.profileId);
|
|
136
|
+
return ok({
|
|
137
|
+
message: `Profile "${args.profileId}" deleted`,
|
|
138
|
+
});
|
|
139
|
+
} catch (e) {
|
|
140
|
+
return err(e instanceof Error ? e.message : "Failed to delete profile");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
),
|
|
47
144
|
];
|
|
48
145
|
}
|
|
@@ -85,5 +85,38 @@ export function projectTools(ctx: ToolContext) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
),
|
|
88
|
+
|
|
89
|
+
defineTool(
|
|
90
|
+
"delete_project",
|
|
91
|
+
"Permanently delete a project and all its resources (tasks, tables, schedules, " +
|
|
92
|
+
"documents, app instances). This is irreversible. Use to clean up orphaned or " +
|
|
93
|
+
"unwanted projects. Always confirm with the user before calling.",
|
|
94
|
+
{
|
|
95
|
+
projectId: z
|
|
96
|
+
.string()
|
|
97
|
+
.describe("The ID of the project to delete"),
|
|
98
|
+
},
|
|
99
|
+
async (args) => {
|
|
100
|
+
try {
|
|
101
|
+
const { deleteProjectCascade } = await import(
|
|
102
|
+
"@/lib/data/delete-project"
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const deleted = deleteProjectCascade(args.projectId);
|
|
106
|
+
if (!deleted) {
|
|
107
|
+
return err(`Project "${args.projectId}" not found`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return ok({
|
|
111
|
+
projectId: args.projectId,
|
|
112
|
+
message: `Project "${args.projectId}" and all its resources have been deleted.`,
|
|
113
|
+
});
|
|
114
|
+
} catch (e) {
|
|
115
|
+
return err(
|
|
116
|
+
e instanceof Error ? e.message : "Failed to delete project",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
),
|
|
88
121
|
];
|
|
89
122
|
}
|
|
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { db } from "@/lib/db";
|
|
4
4
|
import { schedules } from "@/lib/db/schema";
|
|
5
5
|
import { eq, and, desc } from "drizzle-orm";
|
|
6
|
-
import { ok, err, type ToolContext } from "./helpers";
|
|
6
|
+
import { ok, err, resolveEntityId, type ToolContext } from "./helpers";
|
|
7
7
|
import { analyzePromptEfficiency } from "@/lib/schedules/prompt-analyzer";
|
|
8
8
|
|
|
9
9
|
const VALID_SCHEDULE_STATUSES = [
|
|
@@ -69,6 +69,13 @@ export function scheduleTools(ctx: ToolContext) {
|
|
|
69
69
|
.number()
|
|
70
70
|
.optional()
|
|
71
71
|
.describe("Auto-expire after this many hours"),
|
|
72
|
+
maxTurns: z
|
|
73
|
+
.number()
|
|
74
|
+
.int()
|
|
75
|
+
.min(10)
|
|
76
|
+
.max(500)
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Hard cap on turns per firing (10-500). Omit to inherit the system default."),
|
|
72
79
|
},
|
|
73
80
|
async (args) => {
|
|
74
81
|
try {
|
|
@@ -147,6 +154,8 @@ export function scheduleTools(ctx: ToolContext) {
|
|
|
147
154
|
recurs: true,
|
|
148
155
|
status: "active",
|
|
149
156
|
maxFirings: args.maxFirings ?? null,
|
|
157
|
+
maxTurns: args.maxTurns ?? null,
|
|
158
|
+
maxTurnsSetAt: args.maxTurns !== undefined ? now : null,
|
|
150
159
|
firingCount: 0,
|
|
151
160
|
expiresAt,
|
|
152
161
|
nextFireAt,
|
|
@@ -184,13 +193,17 @@ export function scheduleTools(ctx: ToolContext) {
|
|
|
184
193
|
},
|
|
185
194
|
async (args) => {
|
|
186
195
|
try {
|
|
196
|
+
const resolved = await resolveEntityId(schedules, schedules.id, args.scheduleId);
|
|
197
|
+
if ("error" in resolved) return err(resolved.error);
|
|
198
|
+
const scheduleId = resolved.id;
|
|
199
|
+
|
|
187
200
|
const schedule = await db
|
|
188
201
|
.select()
|
|
189
202
|
.from(schedules)
|
|
190
|
-
.where(eq(schedules.id,
|
|
203
|
+
.where(eq(schedules.id, scheduleId))
|
|
191
204
|
.get();
|
|
192
205
|
|
|
193
|
-
if (!schedule) return err(`Schedule not found: ${
|
|
206
|
+
if (!schedule) return err(`Schedule not found: ${scheduleId}`);
|
|
194
207
|
ctx.onToolResult?.("get_schedule", schedule);
|
|
195
208
|
return ok(schedule);
|
|
196
209
|
} catch (e) {
|
|
@@ -216,16 +229,28 @@ export function scheduleTools(ctx: ToolContext) {
|
|
|
216
229
|
.describe("New status (use 'paused' to pause, 'active' to resume)"),
|
|
217
230
|
assignedAgent: z.string().optional().describe("New runtime ID"),
|
|
218
231
|
agentProfile: z.string().optional().describe("New agent profile"),
|
|
232
|
+
maxTurns: z
|
|
233
|
+
.number()
|
|
234
|
+
.int()
|
|
235
|
+
.min(10)
|
|
236
|
+
.max(500)
|
|
237
|
+
.optional()
|
|
238
|
+
.nullable()
|
|
239
|
+
.describe("Hard cap on turns per firing (10-500). Pass null to clear an override back to the system default."),
|
|
219
240
|
},
|
|
220
241
|
async (args) => {
|
|
221
242
|
try {
|
|
243
|
+
const resolved = await resolveEntityId(schedules, schedules.id, args.scheduleId);
|
|
244
|
+
if ("error" in resolved) return err(resolved.error);
|
|
245
|
+
const scheduleId = resolved.id;
|
|
246
|
+
|
|
222
247
|
const existing = await db
|
|
223
248
|
.select()
|
|
224
249
|
.from(schedules)
|
|
225
|
-
.where(eq(schedules.id,
|
|
250
|
+
.where(eq(schedules.id, scheduleId))
|
|
226
251
|
.get();
|
|
227
252
|
|
|
228
|
-
if (!existing) return err(`Schedule not found: ${
|
|
253
|
+
if (!existing) return err(`Schedule not found: ${scheduleId}`);
|
|
229
254
|
|
|
230
255
|
const updates: Record<string, unknown> = { updatedAt: new Date() };
|
|
231
256
|
if (args.name !== undefined) updates.name = args.name;
|
|
@@ -233,6 +258,10 @@ export function scheduleTools(ctx: ToolContext) {
|
|
|
233
258
|
if (args.status !== undefined) updates.status = args.status;
|
|
234
259
|
if (args.assignedAgent !== undefined) updates.assignedAgent = args.assignedAgent;
|
|
235
260
|
if (args.agentProfile !== undefined) updates.agentProfile = args.agentProfile;
|
|
261
|
+
if (args.maxTurns !== undefined) {
|
|
262
|
+
updates.maxTurns = args.maxTurns;
|
|
263
|
+
updates.maxTurnsSetAt = args.maxTurns === null ? null : new Date();
|
|
264
|
+
}
|
|
236
265
|
|
|
237
266
|
if (args.interval) {
|
|
238
267
|
const { parseInterval, computeNextFireTime } = await import(
|
|
@@ -267,12 +296,12 @@ export function scheduleTools(ctx: ToolContext) {
|
|
|
267
296
|
await db
|
|
268
297
|
.update(schedules)
|
|
269
298
|
.set(updates)
|
|
270
|
-
.where(eq(schedules.id,
|
|
299
|
+
.where(eq(schedules.id, scheduleId));
|
|
271
300
|
|
|
272
301
|
const [schedule] = await db
|
|
273
302
|
.select()
|
|
274
303
|
.from(schedules)
|
|
275
|
-
.where(eq(schedules.id,
|
|
304
|
+
.where(eq(schedules.id, scheduleId));
|
|
276
305
|
|
|
277
306
|
ctx.onToolResult?.("update_schedule", schedule);
|
|
278
307
|
return ok(schedule);
|
|
@@ -290,16 +319,20 @@ export function scheduleTools(ctx: ToolContext) {
|
|
|
290
319
|
},
|
|
291
320
|
async (args) => {
|
|
292
321
|
try {
|
|
322
|
+
const resolved = await resolveEntityId(schedules, schedules.id, args.scheduleId);
|
|
323
|
+
if ("error" in resolved) return err(resolved.error);
|
|
324
|
+
const scheduleId = resolved.id;
|
|
325
|
+
|
|
293
326
|
const existing = await db
|
|
294
327
|
.select()
|
|
295
328
|
.from(schedules)
|
|
296
|
-
.where(eq(schedules.id,
|
|
329
|
+
.where(eq(schedules.id, scheduleId))
|
|
297
330
|
.get();
|
|
298
331
|
|
|
299
|
-
if (!existing) return err(`Schedule not found: ${
|
|
332
|
+
if (!existing) return err(`Schedule not found: ${scheduleId}`);
|
|
300
333
|
|
|
301
|
-
await db.delete(schedules).where(eq(schedules.id,
|
|
302
|
-
return ok({ message: "Schedule deleted", scheduleId
|
|
334
|
+
await db.delete(schedules).where(eq(schedules.id, scheduleId));
|
|
335
|
+
return ok({ message: "Schedule deleted", scheduleId, name: existing.name });
|
|
303
336
|
} catch (e) {
|
|
304
337
|
return err(e instanceof Error ? e.message : "Failed to delete schedule");
|
|
305
338
|
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineTool } from "../tool-registry";
|
|
3
|
+
import { ok, err, type ToolContext } from "./helpers";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Stagent MCP tools for conversation-scoped skill management.
|
|
7
|
+
*
|
|
8
|
+
* Primary consumer: Ollama — the HTTP chat-completion API has no native
|
|
9
|
+
* concept of skills, so Stagent takes over: activate a skill (persist to
|
|
10
|
+
* conversations.active_skill_id) → context builder injects its SKILL.md
|
|
11
|
+
* into Tier 0 of every subsequent turn.
|
|
12
|
+
*
|
|
13
|
+
* Secondary consumer: Claude and Codex runtimes may also call these tools
|
|
14
|
+
* for a programmatic skill-activation path alongside their native Skill
|
|
15
|
+
* handling. The tools themselves are runtime-agnostic — they just bind
|
|
16
|
+
* skill IDs to conversation rows.
|
|
17
|
+
*
|
|
18
|
+
* See `features/chat-ollama-native-skills.md`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// `mergeActiveSkillIds` lives in `@/lib/chat/active-skills` so client code
|
|
22
|
+
// can import the pure helper without pulling this module's `db` import.
|
|
23
|
+
// Re-exported here for back-compat with existing callers (tests, etc.).
|
|
24
|
+
import { mergeActiveSkillIds } from "@/lib/chat/active-skills";
|
|
25
|
+
export { mergeActiveSkillIds };
|
|
26
|
+
|
|
27
|
+
export function skillTools(_ctx: ToolContext) {
|
|
28
|
+
return [
|
|
29
|
+
defineTool(
|
|
30
|
+
"list_skills",
|
|
31
|
+
"List all Stagent-discoverable skills across user (~/.claude, ~/.codex) and project (.claude, .agents) scopes. Returns id, name, tool persona, scope, and a short preview for each. Pass `enriched: true` for additional per-skill metadata (healthScore, syncStatus, linkedProfileId). Read-only.",
|
|
32
|
+
{
|
|
33
|
+
enriched: z
|
|
34
|
+
.boolean()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe(
|
|
37
|
+
"When true, include healthScore ('healthy'|'stale'|'aging'|'unknown'), syncStatus ('synced'|'claude-only'|'codex-only'|'shared'), and linkedProfileId per skill."
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
async (args) => {
|
|
41
|
+
try {
|
|
42
|
+
if (args.enriched) {
|
|
43
|
+
const { listSkillsEnriched } = await import("@/lib/environment/list-skills");
|
|
44
|
+
const skills = listSkillsEnriched();
|
|
45
|
+
return ok({
|
|
46
|
+
count: skills.length,
|
|
47
|
+
skills: skills.map((s) => ({
|
|
48
|
+
id: s.id,
|
|
49
|
+
name: s.name,
|
|
50
|
+
tool: s.tool,
|
|
51
|
+
scope: s.scope,
|
|
52
|
+
preview: s.preview,
|
|
53
|
+
sizeBytes: s.sizeBytes,
|
|
54
|
+
healthScore: s.healthScore,
|
|
55
|
+
syncStatus: s.syncStatus,
|
|
56
|
+
linkedProfileId: s.linkedProfileId,
|
|
57
|
+
})),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const { listSkills } = await import("@/lib/environment/list-skills");
|
|
61
|
+
const skills = listSkills();
|
|
62
|
+
return ok({
|
|
63
|
+
count: skills.length,
|
|
64
|
+
skills: skills.map((s) => ({
|
|
65
|
+
id: s.id,
|
|
66
|
+
name: s.name,
|
|
67
|
+
tool: s.tool,
|
|
68
|
+
scope: s.scope,
|
|
69
|
+
preview: s.preview,
|
|
70
|
+
sizeBytes: s.sizeBytes,
|
|
71
|
+
})),
|
|
72
|
+
});
|
|
73
|
+
} catch (e) {
|
|
74
|
+
return err(e instanceof Error ? e.message : "list_skills failed");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
|
|
79
|
+
defineTool(
|
|
80
|
+
"get_skill",
|
|
81
|
+
"Return the full SKILL.md content plus metadata for a single skill, identified by the id returned from list_skills. Use this to preview a skill before activating it.",
|
|
82
|
+
{
|
|
83
|
+
id: z
|
|
84
|
+
.string()
|
|
85
|
+
.describe("Opaque skill ID (from list_skills). Typically the relative path."),
|
|
86
|
+
},
|
|
87
|
+
async (args) => {
|
|
88
|
+
try {
|
|
89
|
+
const { getSkill } = await import("@/lib/environment/list-skills");
|
|
90
|
+
const skill = getSkill(args.id);
|
|
91
|
+
if (!skill) return err(`Skill not found: ${args.id}`);
|
|
92
|
+
return ok({
|
|
93
|
+
id: skill.id,
|
|
94
|
+
name: skill.name,
|
|
95
|
+
tool: skill.tool,
|
|
96
|
+
scope: skill.scope,
|
|
97
|
+
sizeBytes: skill.sizeBytes,
|
|
98
|
+
content: skill.content,
|
|
99
|
+
});
|
|
100
|
+
} catch (e) {
|
|
101
|
+
return err(e instanceof Error ? e.message : "get_skill failed");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
),
|
|
105
|
+
|
|
106
|
+
defineTool(
|
|
107
|
+
"activate_skill",
|
|
108
|
+
"Activate a skill on a conversation. While active, the skill's SKILL.md is injected into the system prompt on every subsequent turn. Default mode 'replace' clears any prior active skills and binds just this one. Pass mode='add' to compose multiple skills (gated by runtime — Ollama refuses; Claude/Codex/direct allow up to 3). Pass force=true to skip conflict warnings on add.",
|
|
109
|
+
{
|
|
110
|
+
conversationId: z.string().describe("ID of the conversation to bind the skill to."),
|
|
111
|
+
skillId: z.string().describe("Opaque skill ID from list_skills (typically the relative path)."),
|
|
112
|
+
mode: z
|
|
113
|
+
.enum(["replace", "add"])
|
|
114
|
+
.optional()
|
|
115
|
+
.default("replace")
|
|
116
|
+
.describe("'replace' (default) clears prior active skills; 'add' appends — runtime must support composition."),
|
|
117
|
+
force: z
|
|
118
|
+
.boolean()
|
|
119
|
+
.optional()
|
|
120
|
+
.default(false)
|
|
121
|
+
.describe("When mode='add', skip the conflict heuristic check and add anyway."),
|
|
122
|
+
},
|
|
123
|
+
async (args) => {
|
|
124
|
+
const { activateSkill } = await import("@/lib/chat/skill-composition");
|
|
125
|
+
const result = await activateSkill({
|
|
126
|
+
conversationId: args.conversationId,
|
|
127
|
+
skillId: args.skillId,
|
|
128
|
+
mode: args.mode,
|
|
129
|
+
force: args.force,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (result.kind === "error") return err(result.message);
|
|
133
|
+
|
|
134
|
+
if (result.kind === "conflicts") {
|
|
135
|
+
return ok({
|
|
136
|
+
conversationId: args.conversationId,
|
|
137
|
+
requiresConfirmation: true,
|
|
138
|
+
conflicts: result.conflicts,
|
|
139
|
+
hint: "Re-call activate_skill with force=true to add anyway",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// kind === "ok"
|
|
144
|
+
if (result.note === "skill already active") {
|
|
145
|
+
return ok({
|
|
146
|
+
conversationId: args.conversationId,
|
|
147
|
+
activeSkillIds: result.activeSkillIds,
|
|
148
|
+
note: result.note,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return ok({
|
|
153
|
+
conversationId: args.conversationId,
|
|
154
|
+
activatedSkillId: result.activatedSkillId,
|
|
155
|
+
activeSkillIds: result.activeSkillIds,
|
|
156
|
+
skillName: result.skillName,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
|
|
161
|
+
defineTool(
|
|
162
|
+
"deactivate_skill",
|
|
163
|
+
"Clear the active skill on a conversation. After this call, subsequent turns will not include any Stagent-injected SKILL.md in the system prompt.",
|
|
164
|
+
{
|
|
165
|
+
conversationId: z
|
|
166
|
+
.string()
|
|
167
|
+
.describe("ID of the conversation to clear the active skill from."),
|
|
168
|
+
},
|
|
169
|
+
async (args) => {
|
|
170
|
+
const { deactivateSkill } = await import("@/lib/chat/skill-composition");
|
|
171
|
+
const result = await deactivateSkill({ conversationId: args.conversationId });
|
|
172
|
+
|
|
173
|
+
if (result.kind === "error") return err(result.message);
|
|
174
|
+
|
|
175
|
+
return ok({
|
|
176
|
+
conversationId: args.conversationId,
|
|
177
|
+
previousSkillId: result.previousSkillId,
|
|
178
|
+
activeSkillId: null,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
];
|
|
183
|
+
}
|