stagent 0.9.6 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -44
- package/dist/cli.js +66 -18
- package/docs/.coverage-gaps.json +144 -56
- package/docs/.last-generated +1 -1
- package/docs/features/agent-intelligence.md +12 -2
- package/docs/features/chat.md +40 -5
- package/docs/features/cost-usage.md +1 -1
- package/docs/features/documents.md +5 -2
- package/docs/features/inbox-notifications.md +10 -2
- package/docs/features/keyboard-navigation.md +12 -3
- package/docs/features/provider-runtimes.md +20 -2
- package/docs/features/schedules.md +32 -4
- package/docs/features/settings.md +28 -5
- package/docs/features/shared-components.md +7 -3
- package/docs/features/tables.md +11 -2
- package/docs/features/tool-permissions.md +6 -2
- package/docs/features/workflows.md +14 -4
- package/docs/index.md +1 -1
- package/docs/journeys/developer.md +39 -2
- package/docs/journeys/personal-use.md +32 -8
- package/docs/journeys/power-user.md +45 -14
- package/docs/journeys/work-use.md +17 -8
- package/docs/manifest.json +15 -15
- package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
- package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
- package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
- package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
- package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
- package/docs/superpowers/plans/2026-04-14-chat-command-namespace-refactor.md +1390 -0
- package/docs/superpowers/plans/2026-04-14-chat-environment-integration.md +1561 -0
- package/docs/superpowers/plans/2026-04-14-chat-polish-bundle-v1.md +1219 -0
- package/docs/superpowers/plans/2026-04-14-chat-session-persistence-provider-closeout.md +399 -0
- package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
- package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
- package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
- package/next.config.mjs +1 -0
- package/package.json +3 -2
- package/src/__tests__/instrumentation-smoke.test.ts +15 -0
- package/src/app/analytics/page.tsx +1 -21
- package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
- package/src/app/api/chat/conversations/[id]/skills/__tests__/activate.test.ts +141 -0
- package/src/app/api/chat/conversations/[id]/skills/activate/route.ts +74 -0
- package/src/app/api/chat/conversations/[id]/skills/deactivate/route.ts +33 -0
- package/src/app/api/chat/export/route.ts +52 -0
- package/src/app/api/chat/files/search/route.ts +50 -0
- package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
- package/src/app/api/environment/rescan-if-stale/__tests__/route.test.ts +45 -0
- package/src/app/api/environment/rescan-if-stale/route.ts +23 -0
- package/src/app/api/environment/skills/route.ts +13 -0
- package/src/app/api/instance/config/route.ts +41 -0
- package/src/app/api/instance/init/route.ts +34 -0
- package/src/app/api/instance/upgrade/check/route.ts +26 -0
- package/src/app/api/instance/upgrade/route.ts +96 -0
- package/src/app/api/instance/upgrade/status/route.ts +35 -0
- package/src/app/api/memory/route.ts +0 -11
- package/src/app/api/notifications/route.ts +4 -2
- package/src/app/api/projects/[id]/route.ts +5 -155
- package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
- package/src/app/api/schedules/[id]/execute/route.ts +111 -0
- package/src/app/api/schedules/[id]/route.ts +9 -1
- package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
- package/src/app/api/schedules/route.ts +3 -12
- package/src/app/api/settings/chat/pins/route.ts +94 -0
- package/src/app/api/settings/chat/saved-searches/__tests__/route.test.ts +119 -0
- package/src/app/api/settings/chat/saved-searches/route.ts +79 -0
- package/src/app/api/settings/environment/route.ts +26 -0
- package/src/app/api/settings/openai/login/route.ts +22 -0
- package/src/app/api/settings/openai/logout/route.ts +7 -0
- package/src/app/api/settings/openai/route.ts +21 -1
- package/src/app/api/settings/providers/route.ts +35 -8
- package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
- package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
- package/src/app/api/tables/[id]/enrich/route.ts +147 -0
- package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
- package/src/app/api/tasks/[id]/execute/route.ts +52 -33
- package/src/app/api/tasks/[id]/respond/route.ts +31 -15
- package/src/app/api/tasks/[id]/resume/route.ts +24 -3
- package/src/app/api/workflows/[id]/resume/route.ts +59 -0
- package/src/app/api/workflows/[id]/status/route.ts +22 -8
- package/src/app/api/workspace/context/route.ts +2 -0
- package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
- package/src/app/chat/page.tsx +11 -0
- package/src/app/documents/page.tsx +4 -1
- package/src/app/inbox/page.tsx +12 -5
- package/src/app/layout.tsx +42 -21
- package/src/app/page.tsx +0 -2
- package/src/app/settings/page.tsx +8 -9
- package/src/components/chat/__tests__/capability-banner.test.tsx +38 -0
- package/src/components/chat/__tests__/chat-session-provider.test.tsx +573 -0
- package/src/components/chat/__tests__/skill-row.test.tsx +91 -0
- package/src/components/chat/capability-banner.tsx +68 -0
- package/src/components/chat/chat-command-popover.tsx +670 -49
- package/src/components/chat/chat-input.tsx +104 -10
- package/src/components/chat/chat-message.tsx +12 -3
- package/src/components/chat/chat-session-provider.tsx +790 -0
- package/src/components/chat/chat-shell.tsx +151 -401
- package/src/components/chat/command-tab-bar.tsx +68 -0
- package/src/components/chat/conversation-template-picker.tsx +421 -0
- package/src/components/chat/help-dialog.tsx +39 -0
- package/src/components/chat/skill-composition-conflict-dialog.tsx +96 -0
- package/src/components/chat/skill-row.tsx +147 -0
- package/src/components/documents/document-browser.tsx +37 -19
- package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
- package/src/components/instance/instance-section.tsx +382 -0
- package/src/components/instance/upgrade-badge.tsx +219 -0
- package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
- package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
- package/src/components/notifications/__tests__/permission-response-actions.test.tsx +70 -0
- package/src/components/notifications/batch-proposal-review.tsx +20 -5
- package/src/components/notifications/inbox-list.tsx +11 -2
- package/src/components/notifications/notification-item.tsx +56 -2
- package/src/components/notifications/pending-approval-host.tsx +56 -37
- package/src/components/notifications/permission-response-actions.tsx +155 -1
- package/src/components/schedules/schedule-create-sheet.tsx +19 -1
- package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
- package/src/components/schedules/schedule-form.tsx +31 -0
- package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
- package/src/components/settings/auth-method-selector.tsx +19 -4
- package/src/components/settings/auth-status-badge.tsx +28 -3
- package/src/components/settings/environment-section.tsx +102 -0
- package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
- package/src/components/settings/openai-runtime-section.tsx +7 -1
- package/src/components/settings/providers-runtimes-section.tsx +138 -19
- package/src/components/shared/__tests__/filter-hint.test.tsx +40 -0
- package/src/components/shared/__tests__/saved-searches-manager.test.tsx +147 -0
- package/src/components/shared/app-sidebar.tsx +4 -3
- package/src/components/shared/command-palette.tsx +266 -7
- package/src/components/shared/filter-hint.tsx +70 -0
- package/src/components/shared/filter-input.tsx +59 -0
- package/src/components/shared/saved-searches-manager.tsx +199 -0
- package/src/components/shared/theme-toggle.tsx +5 -24
- package/src/components/shared/workspace-indicator.tsx +61 -2
- package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
- package/src/components/tables/table-create-sheet.tsx +4 -0
- package/src/components/tables/table-enrichment-runs.tsx +103 -0
- package/src/components/tables/table-enrichment-sheet.tsx +538 -0
- package/src/components/tables/table-spreadsheet.tsx +29 -5
- package/src/components/tables/table-toolbar.tsx +10 -1
- package/src/components/tasks/kanban-board.tsx +1 -0
- package/src/components/tasks/kanban-column.tsx +53 -14
- package/src/components/tasks/task-bento-grid.tsx +31 -2
- package/src/components/tasks/task-card.tsx +29 -3
- package/src/components/tasks/task-chip-bar.tsx +54 -1
- package/src/components/tasks/task-result-renderer.tsx +1 -1
- package/src/components/workflows/delay-step-body.tsx +109 -0
- package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
- package/src/components/workflows/loop-status-view.tsx +1 -1
- package/src/components/workflows/shared/step-result.tsx +78 -0
- package/src/components/workflows/shared/workflow-header.tsx +141 -0
- package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
- package/src/components/workflows/swarm-dashboard.tsx +2 -15
- package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
- package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
- package/src/components/workflows/workflow-form-view.tsx +133 -16
- package/src/components/workflows/workflow-status-view.tsx +30 -740
- package/src/hooks/__tests__/use-chat-autocomplete-tabs.test.ts +47 -0
- package/src/hooks/__tests__/use-saved-searches.test.ts +70 -0
- package/src/hooks/use-active-skills.ts +110 -0
- package/src/hooks/use-chat-autocomplete.ts +120 -7
- package/src/hooks/use-enriched-skills.ts +19 -0
- package/src/hooks/use-pinned-entries.ts +104 -0
- package/src/hooks/use-recent-user-messages.ts +19 -0
- package/src/hooks/use-saved-searches.ts +142 -0
- package/src/instrumentation-node.ts +94 -0
- package/src/instrumentation.ts +4 -48
- package/src/lib/agents/__tests__/claude-agent-sdk-options.test.ts +56 -0
- package/src/lib/agents/__tests__/claude-agent.test.ts +212 -0
- package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
- package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
- package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
- package/src/lib/agents/__tests__/task-dispatch.test.ts +166 -0
- package/src/lib/agents/__tests__/tool-permissions.test.ts +60 -0
- package/src/lib/agents/claude-agent.ts +217 -21
- package/src/lib/agents/execution-manager.ts +0 -35
- package/src/lib/agents/handoff/bus.ts +2 -2
- package/src/lib/agents/learned-context.ts +0 -12
- package/src/lib/agents/learning-session.ts +18 -5
- package/src/lib/agents/profiles/__tests__/list-fused-profiles.test.ts +110 -0
- package/src/lib/agents/profiles/__tests__/registry.test.ts +53 -4
- package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +97 -0
- package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +36 -0
- package/src/lib/agents/profiles/list-fused-profiles.ts +104 -0
- package/src/lib/agents/profiles/registry.ts +18 -0
- package/src/lib/agents/profiles/types.ts +7 -1
- package/src/lib/agents/router.ts +3 -6
- package/src/lib/agents/runtime/__tests__/catalog.test.ts +130 -0
- package/src/lib/agents/runtime/__tests__/execution-target.test.ts +183 -0
- package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
- package/src/lib/agents/runtime/anthropic-direct.ts +8 -0
- package/src/lib/agents/runtime/catalog.ts +121 -0
- package/src/lib/agents/runtime/claude-sdk.ts +32 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
- package/src/lib/agents/runtime/execution-target.ts +456 -0
- package/src/lib/agents/runtime/index.ts +4 -0
- package/src/lib/agents/runtime/launch-failure.ts +101 -0
- package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
- package/src/lib/agents/runtime/openai-codex.ts +64 -60
- package/src/lib/agents/runtime/openai-direct.ts +8 -0
- package/src/lib/agents/runtime/types.ts +8 -0
- package/src/lib/agents/task-dispatch.ts +220 -0
- package/src/lib/agents/tool-permissions.ts +16 -1
- package/src/lib/book/chapter-mapping.ts +11 -0
- package/src/lib/book/content.ts +10 -0
- package/src/lib/chat/__tests__/active-skill-injection.test.ts +261 -0
- package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
- package/src/lib/chat/__tests__/clean-filter-input.test.ts +68 -0
- package/src/lib/chat/__tests__/command-tabs.test.ts +68 -0
- package/src/lib/chat/__tests__/context-builder-files.test.ts +112 -0
- package/src/lib/chat/__tests__/dismissals.test.ts +65 -0
- package/src/lib/chat/__tests__/engine-sdk-options.test.ts +117 -0
- package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
- package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
- package/src/lib/chat/__tests__/skill-conflict.test.ts +35 -0
- package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
- package/src/lib/chat/__tests__/types.test.ts +28 -0
- package/src/lib/chat/active-skills.ts +31 -0
- package/src/lib/chat/active-streams.ts +27 -0
- package/src/lib/chat/clean-filter-input.ts +30 -0
- package/src/lib/chat/codex-engine.ts +46 -24
- package/src/lib/chat/command-tabs.ts +61 -0
- package/src/lib/chat/context-builder.ts +146 -4
- package/src/lib/chat/dismissals.ts +73 -0
- package/src/lib/chat/engine.ts +159 -18
- package/src/lib/chat/files/__tests__/search.test.ts +135 -0
- package/src/lib/chat/files/expand-mention.ts +76 -0
- package/src/lib/chat/files/search.ts +99 -0
- package/src/lib/chat/reconcile.ts +117 -0
- package/src/lib/chat/skill-composition.ts +210 -0
- package/src/lib/chat/skill-conflict.ts +105 -0
- package/src/lib/chat/stagent-tools.ts +7 -19
- package/src/lib/chat/stream-telemetry.ts +137 -0
- package/src/lib/chat/suggested-prompts.ts +28 -1
- package/src/lib/chat/system-prompt.ts +48 -1
- package/src/lib/chat/tool-catalog.ts +35 -4
- package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
- package/src/lib/chat/tools/__tests__/profile-tools.test.ts +51 -0
- package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
- package/src/lib/chat/tools/__tests__/settings-tools.test.ts +294 -0
- package/src/lib/chat/tools/__tests__/skill-tools.test.ts +474 -0
- package/src/lib/chat/tools/__tests__/task-tools.test.ts +399 -0
- package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +351 -0
- package/src/lib/chat/tools/blueprint-tools.ts +190 -0
- package/src/lib/chat/tools/document-tools.ts +29 -13
- package/src/lib/chat/tools/helpers.ts +41 -0
- package/src/lib/chat/tools/notification-tools.ts +9 -5
- package/src/lib/chat/tools/profile-tools.ts +120 -23
- package/src/lib/chat/tools/project-tools.ts +33 -0
- package/src/lib/chat/tools/schedule-tools.ts +44 -11
- package/src/lib/chat/tools/skill-tools.ts +183 -0
- package/src/lib/chat/tools/table-tools.ts +71 -0
- package/src/lib/chat/tools/task-tools.ts +89 -21
- package/src/lib/chat/tools/workflow-tools.ts +275 -32
- package/src/lib/chat/types.ts +15 -0
- package/src/lib/constants/settings.ts +10 -18
- package/src/lib/data/__tests__/clear.test.ts +56 -2
- package/src/lib/data/clear.ts +17 -16
- package/src/lib/data/delete-project.ts +171 -0
- package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
- package/src/lib/db/bootstrap.ts +62 -16
- package/src/lib/db/index.ts +5 -0
- package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
- package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
- package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
- package/src/lib/db/migrations/0026_drop_license.sql +3 -0
- package/src/lib/db/migrations/meta/_journal.json +21 -0
- package/src/lib/db/schema.ts +94 -23
- package/src/lib/environment/__tests__/auto-promote.test.ts +132 -0
- package/src/lib/environment/__tests__/list-skills-enriched.test.ts +55 -0
- package/src/lib/environment/__tests__/skill-enrichment.test.ts +129 -0
- package/src/lib/environment/__tests__/skill-recommendations.test.ts +87 -0
- package/src/lib/environment/data.ts +9 -0
- package/src/lib/environment/list-skills.ts +176 -0
- package/src/lib/environment/parsers/__tests__/skill.test.ts +54 -0
- package/src/lib/environment/parsers/skill.ts +26 -5
- package/src/lib/environment/profile-generator.ts +54 -0
- package/src/lib/environment/skill-enrichment.ts +106 -0
- package/src/lib/environment/skill-recommendations.ts +66 -0
- package/src/lib/environment/workspace-context.ts +13 -1
- package/src/lib/filters/__tests__/parse.quoted.test.ts +40 -0
- package/src/lib/filters/__tests__/parse.test.ts +135 -0
- package/src/lib/filters/parse.ts +86 -0
- package/src/lib/import/dedup.ts +4 -54
- package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
- package/src/lib/instance/__tests__/detect.test.ts +115 -0
- package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
- package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
- package/src/lib/instance/__tests__/settings.test.ts +83 -0
- package/src/lib/instance/__tests__/upgrade-poller.test.ts +181 -0
- package/src/lib/instance/bootstrap.ts +270 -0
- package/src/lib/instance/detect.ts +49 -0
- package/src/lib/instance/fingerprint.ts +76 -0
- package/src/lib/instance/git-ops.ts +95 -0
- package/src/lib/instance/settings.ts +61 -0
- package/src/lib/instance/types.ts +77 -0
- package/src/lib/instance/upgrade-poller.ts +205 -0
- package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
- package/src/lib/notifications/visibility.ts +33 -0
- package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
- package/src/lib/schedules/__tests__/config.test.ts +62 -0
- package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
- package/src/lib/schedules/__tests__/integration.test.ts +82 -0
- package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
- package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
- package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
- package/src/lib/schedules/collision-check.ts +105 -0
- package/src/lib/schedules/config.ts +53 -0
- package/src/lib/schedules/scheduler.ts +236 -17
- package/src/lib/schedules/slot-claim.ts +105 -0
- package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
- package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
- package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
- package/src/lib/settings/openai-auth.ts +105 -10
- package/src/lib/settings/openai-login-manager.ts +260 -0
- package/src/lib/settings/runtime-setup.ts +14 -4
- package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
- package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
- package/src/lib/tables/enrichment-planner.ts +454 -0
- package/src/lib/tables/enrichment.ts +328 -0
- package/src/lib/tables/query-builder.ts +5 -2
- package/src/lib/tables/trigger-evaluator.ts +3 -2
- package/src/lib/theme.ts +71 -0
- package/src/lib/usage/ledger.ts +2 -18
- package/src/lib/util/__tests__/similarity.test.ts +106 -0
- package/src/lib/util/similarity.ts +77 -0
- package/src/lib/utils/format-timestamp.ts +24 -0
- package/src/lib/utils/stagent-paths.ts +12 -0
- package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
- package/src/lib/validators/__tests__/settings.test.ts +10 -0
- package/src/lib/validators/blueprint.ts +70 -9
- package/src/lib/validators/profile.ts +2 -2
- package/src/lib/validators/settings.ts +3 -1
- package/src/lib/workflows/__tests__/delay.test.ts +196 -0
- package/src/lib/workflows/__tests__/engine.test.ts +8 -0
- package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
- package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
- package/src/lib/workflows/blueprints/__tests__/render-prompt.test.ts +124 -0
- package/src/lib/workflows/blueprints/instantiator.ts +22 -1
- package/src/lib/workflows/blueprints/render-prompt.ts +71 -0
- package/src/lib/workflows/blueprints/types.ts +16 -2
- package/src/lib/workflows/delay.ts +106 -0
- package/src/lib/workflows/engine.ts +212 -7
- package/src/lib/workflows/loop-executor.ts +349 -24
- package/src/lib/workflows/post-action.ts +91 -0
- package/src/lib/workflows/types.ts +166 -1
- package/src/test/setup.ts +10 -0
- package/src/app/api/license/checkout/route.ts +0 -28
- package/src/app/api/license/portal/route.ts +0 -26
- package/src/app/api/license/route.ts +0 -89
- package/src/app/api/license/usage/route.ts +0 -63
- package/src/app/api/marketplace/browse/route.ts +0 -15
- package/src/app/api/marketplace/import/route.ts +0 -28
- package/src/app/api/marketplace/publish/route.ts +0 -40
- package/src/app/api/onboarding/email/route.ts +0 -53
- package/src/app/api/settings/telemetry/route.ts +0 -14
- package/src/app/api/sync/export/route.ts +0 -54
- package/src/app/api/sync/restore/route.ts +0 -37
- package/src/app/api/sync/sessions/route.ts +0 -24
- package/src/app/auth/callback/route.ts +0 -73
- package/src/app/marketplace/page.tsx +0 -19
- package/src/components/analytics/analytics-gate-card.tsx +0 -101
- package/src/components/marketplace/blueprint-card.tsx +0 -61
- package/src/components/marketplace/marketplace-browser.tsx +0 -131
- package/src/components/onboarding/email-capture-card.tsx +0 -104
- package/src/components/settings/activation-form.tsx +0 -95
- package/src/components/settings/cloud-account-section.tsx +0 -147
- package/src/components/settings/cloud-sync-section.tsx +0 -155
- package/src/components/settings/subscription-section.tsx +0 -410
- package/src/components/settings/telemetry-section.tsx +0 -80
- package/src/components/shared/premium-gate-overlay.tsx +0 -50
- package/src/components/shared/schedule-gate-dialog.tsx +0 -64
- package/src/components/shared/upgrade-banner.tsx +0 -112
- package/src/hooks/use-supabase-auth.ts +0 -79
- package/src/lib/billing/email.ts +0 -54
- package/src/lib/billing/products.ts +0 -80
- package/src/lib/billing/stripe.ts +0 -101
- package/src/lib/cloud/supabase-browser.ts +0 -32
- package/src/lib/cloud/supabase-client.ts +0 -56
- package/src/lib/license/__tests__/features.test.ts +0 -56
- package/src/lib/license/__tests__/key-format.test.ts +0 -88
- package/src/lib/license/__tests__/manager.test.ts +0 -64
- package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
- package/src/lib/license/cloud-validation.ts +0 -60
- package/src/lib/license/features.ts +0 -44
- package/src/lib/license/key-format.ts +0 -101
- package/src/lib/license/limit-check.ts +0 -111
- package/src/lib/license/limit-queries.ts +0 -51
- package/src/lib/license/manager.ts +0 -345
- package/src/lib/license/notifications.ts +0 -59
- package/src/lib/license/tier-limits.ts +0 -71
- package/src/lib/marketplace/marketplace-client.ts +0 -107
- package/src/lib/sync/cloud-sync.ts +0 -235
- package/src/lib/telemetry/conversion-events.ts +0 -71
- package/src/lib/telemetry/queue.ts +0 -122
- package/src/lib/validators/license.ts +0 -33
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
interface WorkflowRow {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
definition: string | null;
|
|
7
|
+
projectId: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { mockWorkflowRows } = vi.hoisted(() => ({
|
|
11
|
+
mockWorkflowRows: { value: [] as WorkflowRow[] },
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
// Minimal drizzle query builder stub — supports
|
|
15
|
+
// db.select({...}).from(table).where(...)
|
|
16
|
+
// by returning a thenable that resolves to mockWorkflowRows.value.
|
|
17
|
+
vi.mock("@/lib/db", () => {
|
|
18
|
+
const builder = {
|
|
19
|
+
from() {
|
|
20
|
+
return this;
|
|
21
|
+
},
|
|
22
|
+
where() {
|
|
23
|
+
return this;
|
|
24
|
+
},
|
|
25
|
+
then<TResolve>(resolve: (rows: WorkflowRow[]) => TResolve) {
|
|
26
|
+
return Promise.resolve(mockWorkflowRows.value).then(resolve);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
db: {
|
|
31
|
+
select: () => builder,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Stub the schema import so drizzle-orm doesn't try to read a real table.
|
|
37
|
+
vi.mock("@/lib/db/schema", () => ({
|
|
38
|
+
workflows: { projectId: "projectId" },
|
|
39
|
+
tasks: {},
|
|
40
|
+
agentLogs: {},
|
|
41
|
+
notifications: {},
|
|
42
|
+
documents: {},
|
|
43
|
+
workflowDocumentInputs: {},
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
// Stub drizzle-orm operators used in workflow-tools.ts — the tests only
|
|
47
|
+
// care about the return value of the builder, not the operator objects.
|
|
48
|
+
vi.mock("drizzle-orm", () => ({
|
|
49
|
+
eq: () => ({}),
|
|
50
|
+
and: () => ({}),
|
|
51
|
+
desc: () => ({}),
|
|
52
|
+
inArray: () => ({}),
|
|
53
|
+
like: () => ({}),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
import { findSimilarWorkflows } from "../workflow-tools";
|
|
57
|
+
|
|
58
|
+
function setRows(rows: WorkflowRow[]) {
|
|
59
|
+
mockWorkflowRows.value = rows;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe("findSimilarWorkflows", () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
setRows([]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("returns [] when projectId is null (no cross-project dedup)", async () => {
|
|
68
|
+
setRows([
|
|
69
|
+
{
|
|
70
|
+
id: "wf1",
|
|
71
|
+
name: "Research Customer Feedback",
|
|
72
|
+
definition: JSON.stringify({
|
|
73
|
+
pattern: "sequence",
|
|
74
|
+
steps: [{ id: "s1", name: "Research customer feedback", prompt: "do research" }],
|
|
75
|
+
}),
|
|
76
|
+
projectId: null,
|
|
77
|
+
},
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
const result = await findSimilarWorkflows(
|
|
81
|
+
null,
|
|
82
|
+
"Research Customer Feedback",
|
|
83
|
+
JSON.stringify({ pattern: "sequence", steps: [] })
|
|
84
|
+
);
|
|
85
|
+
expect(result).toEqual([]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("returns [] when no workflows exist in the project", async () => {
|
|
89
|
+
setRows([]);
|
|
90
|
+
const result = await findSimilarWorkflows(
|
|
91
|
+
"proj_a",
|
|
92
|
+
"Any name",
|
|
93
|
+
JSON.stringify({ pattern: "sequence", steps: [] })
|
|
94
|
+
);
|
|
95
|
+
expect(result).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("matches exact name (case-insensitive) with similarity 1.0", async () => {
|
|
99
|
+
setRows([
|
|
100
|
+
{
|
|
101
|
+
id: "wf1",
|
|
102
|
+
name: "Research Customer Feedback",
|
|
103
|
+
definition: null,
|
|
104
|
+
projectId: "proj_a",
|
|
105
|
+
},
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
const result = await findSimilarWorkflows(
|
|
109
|
+
"proj_a",
|
|
110
|
+
"research customer feedback",
|
|
111
|
+
JSON.stringify({ pattern: "sequence", steps: [] })
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(result).toHaveLength(1);
|
|
115
|
+
expect(result[0]).toMatchObject({
|
|
116
|
+
id: "wf1",
|
|
117
|
+
similarity: 1,
|
|
118
|
+
});
|
|
119
|
+
expect(result[0].reason).toContain("Same name");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("matches on Jaccard similarity over step names + prompts (redesign scenario)", async () => {
|
|
123
|
+
// Simulates the bug scenario: LLM "redesigns" a workflow mid-conversation,
|
|
124
|
+
// using mostly the same vocabulary as the original. The definitions are
|
|
125
|
+
// near-identical (as redesigns typically are in practice) so Jaccard
|
|
126
|
+
// should exceed the 0.7 threshold.
|
|
127
|
+
const sharedSteps = [
|
|
128
|
+
{ id: "s1", name: "Research customer cohort", prompt: "Investigate customer research cohort feedback insights" },
|
|
129
|
+
{ id: "s2", name: "Interview protocol draft", prompt: "Draft customer interview questions protocol script" },
|
|
130
|
+
{ id: "s3", name: "Synthesize findings", prompt: "Summarize customer research findings insights report" },
|
|
131
|
+
];
|
|
132
|
+
setRows([
|
|
133
|
+
{
|
|
134
|
+
id: "wf1",
|
|
135
|
+
name: "Customer Discovery Pipeline",
|
|
136
|
+
definition: JSON.stringify({ pattern: "sequence", steps: sharedSteps }),
|
|
137
|
+
projectId: "proj_a",
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
const result = await findSimilarWorkflows(
|
|
142
|
+
"proj_a",
|
|
143
|
+
"Customer Discovery Workflow v2",
|
|
144
|
+
JSON.stringify({ pattern: "sequence", steps: sharedSteps })
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
expect(result.length).toBeGreaterThanOrEqual(1);
|
|
148
|
+
expect(result[0].id).toBe("wf1");
|
|
149
|
+
expect(result[0].similarity).toBeGreaterThanOrEqual(0.7);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("does NOT match when names and step text are completely different", async () => {
|
|
153
|
+
setRows([
|
|
154
|
+
{
|
|
155
|
+
id: "wf1",
|
|
156
|
+
name: "Deploy frontend release",
|
|
157
|
+
definition: JSON.stringify({
|
|
158
|
+
pattern: "sequence",
|
|
159
|
+
steps: [{ id: "s1", name: "Deploy staging", prompt: "Push release artifact to staging environment" }],
|
|
160
|
+
}),
|
|
161
|
+
projectId: "proj_a",
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
const result = await findSimilarWorkflows(
|
|
166
|
+
"proj_a",
|
|
167
|
+
"Customer interview analysis",
|
|
168
|
+
JSON.stringify({
|
|
169
|
+
pattern: "sequence",
|
|
170
|
+
steps: [{ id: "s2", name: "Summarize interviews", prompt: "Pull insights from recent customer interviews" }],
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
expect(result).toEqual([]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("caps results at 3 and sorts by similarity descending", async () => {
|
|
178
|
+
// Four rows, all exact-name matches (similarity 1.0). Expect exactly 3 returned.
|
|
179
|
+
setRows(
|
|
180
|
+
Array.from({ length: 4 }).map((_, i) => ({
|
|
181
|
+
id: `wf${i}`,
|
|
182
|
+
name: "Duplicate Workflow",
|
|
183
|
+
definition: null,
|
|
184
|
+
projectId: "proj_a",
|
|
185
|
+
}))
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const result = await findSimilarWorkflows(
|
|
189
|
+
"proj_a",
|
|
190
|
+
"Duplicate Workflow",
|
|
191
|
+
JSON.stringify({ pattern: "sequence", steps: [] })
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(result).toHaveLength(3);
|
|
195
|
+
expect(result.every((r) => r.similarity === 1)).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("handles malformed definition JSON without crashing", async () => {
|
|
199
|
+
setRows([
|
|
200
|
+
{
|
|
201
|
+
id: "wf1",
|
|
202
|
+
name: "Legit Workflow",
|
|
203
|
+
definition: "not-json-at-all",
|
|
204
|
+
projectId: "proj_a",
|
|
205
|
+
},
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
// Should not throw — just degrades to name-only comparison.
|
|
209
|
+
const result = await findSimilarWorkflows(
|
|
210
|
+
"proj_a",
|
|
211
|
+
"Legit Workflow",
|
|
212
|
+
"also not json"
|
|
213
|
+
);
|
|
214
|
+
expect(result).toHaveLength(1);
|
|
215
|
+
expect(result[0].similarity).toBe(1); // exact name match
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// ── Legitimate variant tolerance ────────────────────────────────────
|
|
219
|
+
//
|
|
220
|
+
// Regression tests for the concern flagged in the code review of
|
|
221
|
+
// commit b5ed09b: that WORKFLOW_DEDUP_THRESHOLD = 0.7 on a pooled
|
|
222
|
+
// Jaccard over keywords would flag legitimate target-entity variants
|
|
223
|
+
// (e.g. "Enrich contacts" vs "Enrich accounts") as duplicates,
|
|
224
|
+
// eroding trust in the guardrail. Each pair here shares a dominant
|
|
225
|
+
// verb and most of the step structure — the only difference is the
|
|
226
|
+
// target entity noun.
|
|
227
|
+
//
|
|
228
|
+
// Success criterion per spec:
|
|
229
|
+
// - the two "positive-variant" cases must return [] (no match)
|
|
230
|
+
// - the two "guard" cases must still flag duplicates (similarity >=
|
|
231
|
+
// WORKFLOW_DEDUP_THRESHOLD, or exact-name match)
|
|
232
|
+
describe("legitimate variant tolerance", () => {
|
|
233
|
+
it("allows Enrich contacts and Enrich accounts as distinct workflows", async () => {
|
|
234
|
+
setRows([
|
|
235
|
+
{
|
|
236
|
+
id: "wf1",
|
|
237
|
+
name: "Enrich contacts",
|
|
238
|
+
definition: JSON.stringify({
|
|
239
|
+
pattern: "sequence",
|
|
240
|
+
steps: [
|
|
241
|
+
{ id: "s1", name: "Load rows from contacts table", prompt: "Select rows from the contacts table" },
|
|
242
|
+
{ id: "s2", name: "Call enrichment agent", prompt: "Invoke enrichment agent on each row" },
|
|
243
|
+
{ id: "s3", name: "Write back to table", prompt: "Write enriched data back to the contacts table" },
|
|
244
|
+
],
|
|
245
|
+
}),
|
|
246
|
+
projectId: "proj_a",
|
|
247
|
+
},
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
const result = await findSimilarWorkflows(
|
|
251
|
+
"proj_a",
|
|
252
|
+
"Enrich accounts",
|
|
253
|
+
JSON.stringify({
|
|
254
|
+
pattern: "sequence",
|
|
255
|
+
steps: [
|
|
256
|
+
{ id: "s1", name: "Load rows from accounts table", prompt: "Select rows from the accounts table" },
|
|
257
|
+
{ id: "s2", name: "Call enrichment agent", prompt: "Invoke enrichment agent on each row" },
|
|
258
|
+
{ id: "s3", name: "Write back to table", prompt: "Write enriched data back to the accounts table" },
|
|
259
|
+
],
|
|
260
|
+
})
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
expect(result).toEqual([]);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("allows Daily standup digest and Weekly standup digest as distinct workflows", async () => {
|
|
267
|
+
setRows([
|
|
268
|
+
{
|
|
269
|
+
id: "wf1",
|
|
270
|
+
name: "Daily standup digest",
|
|
271
|
+
definition: JSON.stringify({
|
|
272
|
+
pattern: "sequence",
|
|
273
|
+
steps: [
|
|
274
|
+
{ id: "s1", name: "Fetch standup messages", prompt: "Pull daily standup messages from the team channel" },
|
|
275
|
+
{ id: "s2", name: "Summarize daily topics", prompt: "Write a daily digest of key topics and blockers" },
|
|
276
|
+
{ id: "s3", name: "Post digest to channel", prompt: "Post the daily summary digest to the #ops channel" },
|
|
277
|
+
],
|
|
278
|
+
}),
|
|
279
|
+
projectId: "proj_a",
|
|
280
|
+
},
|
|
281
|
+
]);
|
|
282
|
+
|
|
283
|
+
const result = await findSimilarWorkflows(
|
|
284
|
+
"proj_a",
|
|
285
|
+
"Weekly standup digest",
|
|
286
|
+
JSON.stringify({
|
|
287
|
+
pattern: "sequence",
|
|
288
|
+
steps: [
|
|
289
|
+
{ id: "s1", name: "Fetch standup messages", prompt: "Pull weekly standup messages from the team channel" },
|
|
290
|
+
{ id: "s2", name: "Summarize weekly topics", prompt: "Write a weekly digest of key topics and blockers" },
|
|
291
|
+
{ id: "s3", name: "Post digest to channel", prompt: "Post the weekly summary digest to the #ops channel" },
|
|
292
|
+
],
|
|
293
|
+
})
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
expect(result).toEqual([]);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("still blocks exact case-insensitive name matches (guard)", async () => {
|
|
300
|
+
setRows([
|
|
301
|
+
{
|
|
302
|
+
id: "wf1",
|
|
303
|
+
name: "Enrich contacts",
|
|
304
|
+
definition: JSON.stringify({
|
|
305
|
+
pattern: "sequence",
|
|
306
|
+
steps: [
|
|
307
|
+
{ id: "s1", name: "Load rows from contacts table", prompt: "Select rows from the contacts table" },
|
|
308
|
+
],
|
|
309
|
+
}),
|
|
310
|
+
projectId: "proj_a",
|
|
311
|
+
},
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
const result = await findSimilarWorkflows(
|
|
315
|
+
"proj_a",
|
|
316
|
+
"ENRICH CONTACTS", // same name, different case
|
|
317
|
+
JSON.stringify({ pattern: "sequence", steps: [] })
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
expect(result).toHaveLength(1);
|
|
321
|
+
expect(result[0].similarity).toBe(1);
|
|
322
|
+
expect(result[0].reason).toContain("Same name");
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("still blocks near-identical step content with near-identical name (guard)", async () => {
|
|
326
|
+
const sharedSteps = [
|
|
327
|
+
{ id: "s1", name: "Fetch customer segments list", prompt: "Load the customer segments list from BigQuery warehouse" },
|
|
328
|
+
{ id: "s2", name: "Classify each segment bucket", prompt: "Classify each customer segment bucket using ML model" },
|
|
329
|
+
{ id: "s3", name: "Write segments back warehouse", prompt: "Write segment classifications back into BigQuery warehouse" },
|
|
330
|
+
];
|
|
331
|
+
setRows([
|
|
332
|
+
{
|
|
333
|
+
id: "wf1",
|
|
334
|
+
name: "Classify customer segments v1",
|
|
335
|
+
definition: JSON.stringify({ pattern: "sequence", steps: sharedSteps }),
|
|
336
|
+
projectId: "proj_a",
|
|
337
|
+
},
|
|
338
|
+
]);
|
|
339
|
+
|
|
340
|
+
const result = await findSimilarWorkflows(
|
|
341
|
+
"proj_a",
|
|
342
|
+
"Classify customer segments v2",
|
|
343
|
+
JSON.stringify({ pattern: "sequence", steps: sharedSteps })
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
expect(result.length).toBeGreaterThanOrEqual(1);
|
|
347
|
+
expect(result[0].id).toBe("wf1");
|
|
348
|
+
expect(result[0].similarity).toBeGreaterThanOrEqual(0.7);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { defineTool } from "../tool-registry";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ok, err, type ToolContext } from "./helpers";
|
|
4
|
+
|
|
5
|
+
export function blueprintTools(ctx: ToolContext) {
|
|
6
|
+
return [
|
|
7
|
+
defineTool(
|
|
8
|
+
"list_blueprints",
|
|
9
|
+
"List available workflow blueprints. Blueprints are reusable workflow templates with configurable variables. Use instantiate_blueprint to create a workflow from one.",
|
|
10
|
+
{
|
|
11
|
+
domain: z
|
|
12
|
+
.enum(["work", "personal"])
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Filter by domain"),
|
|
15
|
+
search: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Search in name, description, and tags"),
|
|
19
|
+
},
|
|
20
|
+
async (args) => {
|
|
21
|
+
try {
|
|
22
|
+
const { listBlueprints } = await import(
|
|
23
|
+
"@/lib/workflows/blueprints/registry"
|
|
24
|
+
);
|
|
25
|
+
let blueprints = listBlueprints();
|
|
26
|
+
|
|
27
|
+
if (args.domain) {
|
|
28
|
+
blueprints = blueprints.filter((b) => b.domain === args.domain);
|
|
29
|
+
}
|
|
30
|
+
if (args.search) {
|
|
31
|
+
const q = args.search.toLowerCase();
|
|
32
|
+
blueprints = blueprints.filter(
|
|
33
|
+
(b) =>
|
|
34
|
+
b.name.toLowerCase().includes(q) ||
|
|
35
|
+
b.description.toLowerCase().includes(q) ||
|
|
36
|
+
b.tags.some((t) => t.toLowerCase().includes(q))
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return ok(
|
|
41
|
+
blueprints.map((b) => ({
|
|
42
|
+
id: b.id,
|
|
43
|
+
name: b.name,
|
|
44
|
+
description: b.description,
|
|
45
|
+
domain: b.domain,
|
|
46
|
+
pattern: b.pattern,
|
|
47
|
+
tags: b.tags,
|
|
48
|
+
difficulty: b.difficulty,
|
|
49
|
+
estimatedDuration: b.estimatedDuration,
|
|
50
|
+
isBuiltin: b.isBuiltin,
|
|
51
|
+
variableCount: b.variables.length,
|
|
52
|
+
stepCount: b.steps.length,
|
|
53
|
+
}))
|
|
54
|
+
);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
return err(
|
|
57
|
+
e instanceof Error ? e.message : "Failed to list blueprints"
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
),
|
|
62
|
+
|
|
63
|
+
defineTool(
|
|
64
|
+
"get_blueprint",
|
|
65
|
+
"Get full details of a workflow blueprint, including its variables and steps. Use this to understand what inputs are needed before calling instantiate_blueprint.",
|
|
66
|
+
{
|
|
67
|
+
blueprintId: z.string().describe("The blueprint ID to look up"),
|
|
68
|
+
},
|
|
69
|
+
async (args) => {
|
|
70
|
+
try {
|
|
71
|
+
const { getBlueprint } = await import(
|
|
72
|
+
"@/lib/workflows/blueprints/registry"
|
|
73
|
+
);
|
|
74
|
+
const blueprint = getBlueprint(args.blueprintId);
|
|
75
|
+
if (!blueprint)
|
|
76
|
+
return err(`Blueprint not found: ${args.blueprintId}`);
|
|
77
|
+
return ok(blueprint);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
return err(
|
|
80
|
+
e instanceof Error ? e.message : "Failed to get blueprint"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
|
|
86
|
+
defineTool(
|
|
87
|
+
"instantiate_blueprint",
|
|
88
|
+
"Create a draft workflow from a blueprint by filling in its variables. The workflow is created in 'draft' status — use execute_workflow to run it. Call get_blueprint first to see required variables.",
|
|
89
|
+
{
|
|
90
|
+
blueprintId: z
|
|
91
|
+
.string()
|
|
92
|
+
.describe("The blueprint ID to instantiate"),
|
|
93
|
+
variables: z
|
|
94
|
+
.record(z.string(), z.unknown())
|
|
95
|
+
.describe(
|
|
96
|
+
"Key-value map of variable values. Keys are variable IDs from the blueprint. Required variables must be provided."
|
|
97
|
+
),
|
|
98
|
+
projectId: z
|
|
99
|
+
.string()
|
|
100
|
+
.optional()
|
|
101
|
+
.describe(
|
|
102
|
+
"Project ID to attach the workflow to. Omit to use the active project."
|
|
103
|
+
),
|
|
104
|
+
},
|
|
105
|
+
async (args) => {
|
|
106
|
+
try {
|
|
107
|
+
const { instantiateBlueprint } = await import(
|
|
108
|
+
"@/lib/workflows/blueprints/instantiator"
|
|
109
|
+
);
|
|
110
|
+
const effectiveProjectId =
|
|
111
|
+
args.projectId ?? ctx.projectId ?? undefined;
|
|
112
|
+
|
|
113
|
+
const result = await instantiateBlueprint(
|
|
114
|
+
args.blueprintId,
|
|
115
|
+
args.variables,
|
|
116
|
+
effectiveProjectId
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
ctx.onToolResult?.("instantiate_blueprint", result);
|
|
120
|
+
return ok({
|
|
121
|
+
workflowId: result.workflowId,
|
|
122
|
+
name: result.name,
|
|
123
|
+
stepsCount: result.stepsCount,
|
|
124
|
+
skippedSteps: result.skippedSteps,
|
|
125
|
+
status: "draft",
|
|
126
|
+
message:
|
|
127
|
+
"Workflow created from blueprint. Use execute_workflow to run it.",
|
|
128
|
+
});
|
|
129
|
+
} catch (e) {
|
|
130
|
+
return err(
|
|
131
|
+
e instanceof Error ? e.message : "Failed to instantiate blueprint"
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
),
|
|
136
|
+
|
|
137
|
+
defineTool(
|
|
138
|
+
"create_blueprint",
|
|
139
|
+
"Create a custom workflow blueprint from YAML content. The YAML must include id, name, description, version, domain, tags, pattern, variables, and steps. Use get_blueprint on an existing blueprint to see the expected structure.",
|
|
140
|
+
{
|
|
141
|
+
yaml: z
|
|
142
|
+
.string()
|
|
143
|
+
.describe(
|
|
144
|
+
"Full blueprint YAML content. Must validate against the blueprint schema."
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
async (args) => {
|
|
148
|
+
try {
|
|
149
|
+
const { createBlueprint } = await import(
|
|
150
|
+
"@/lib/workflows/blueprints/registry"
|
|
151
|
+
);
|
|
152
|
+
const blueprint = createBlueprint(args.yaml);
|
|
153
|
+
ctx.onToolResult?.("create_blueprint", blueprint);
|
|
154
|
+
return ok({
|
|
155
|
+
id: blueprint.id,
|
|
156
|
+
name: blueprint.name,
|
|
157
|
+
message: "Blueprint created successfully",
|
|
158
|
+
});
|
|
159
|
+
} catch (e) {
|
|
160
|
+
return err(
|
|
161
|
+
e instanceof Error ? e.message : "Failed to create blueprint"
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
),
|
|
166
|
+
|
|
167
|
+
defineTool(
|
|
168
|
+
"delete_blueprint",
|
|
169
|
+
"Delete a custom workflow blueprint. Built-in blueprints cannot be deleted.",
|
|
170
|
+
{
|
|
171
|
+
blueprintId: z.string().describe("The blueprint ID to delete"),
|
|
172
|
+
},
|
|
173
|
+
async (args) => {
|
|
174
|
+
try {
|
|
175
|
+
const { deleteBlueprint } = await import(
|
|
176
|
+
"@/lib/workflows/blueprints/registry"
|
|
177
|
+
);
|
|
178
|
+
deleteBlueprint(args.blueprintId);
|
|
179
|
+
return ok({
|
|
180
|
+
message: `Blueprint "${args.blueprintId}" deleted`,
|
|
181
|
+
});
|
|
182
|
+
} catch (e) {
|
|
183
|
+
return err(
|
|
184
|
+
e instanceof Error ? e.message : "Failed to delete blueprint"
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
),
|
|
189
|
+
];
|
|
190
|
+
}
|
|
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { db } from "@/lib/db";
|
|
4
4
|
import { documents } 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 { access, stat, copyFile, mkdir } from "fs/promises";
|
|
8
8
|
import { basename, extname, join } from "path";
|
|
9
9
|
import crypto from "crypto";
|
|
@@ -105,6 +105,10 @@ export function documentTools(ctx: ToolContext) {
|
|
|
105
105
|
},
|
|
106
106
|
async (args) => {
|
|
107
107
|
try {
|
|
108
|
+
const resolved = await resolveEntityId(documents, documents.id, args.documentId);
|
|
109
|
+
if ("error" in resolved) return err(resolved.error);
|
|
110
|
+
const documentId = resolved.id;
|
|
111
|
+
|
|
108
112
|
const doc = await db
|
|
109
113
|
.select({
|
|
110
114
|
id: documents.id,
|
|
@@ -122,10 +126,10 @@ export function documentTools(ctx: ToolContext) {
|
|
|
122
126
|
updatedAt: documents.updatedAt,
|
|
123
127
|
})
|
|
124
128
|
.from(documents)
|
|
125
|
-
.where(eq(documents.id,
|
|
129
|
+
.where(eq(documents.id, documentId))
|
|
126
130
|
.get();
|
|
127
131
|
|
|
128
|
-
if (!doc) return err(`Document not found: ${
|
|
132
|
+
if (!doc) return err(`Document not found: ${documentId}`);
|
|
129
133
|
ctx.onToolResult?.("get_document", doc);
|
|
130
134
|
return ok(doc);
|
|
131
135
|
} catch (e) {
|
|
@@ -204,13 +208,17 @@ export function documentTools(ctx: ToolContext) {
|
|
|
204
208
|
},
|
|
205
209
|
async (args) => {
|
|
206
210
|
try {
|
|
211
|
+
const resolved = await resolveEntityId(documents, documents.id, args.documentId);
|
|
212
|
+
if ("error" in resolved) return err(resolved.error);
|
|
213
|
+
const documentId = resolved.id;
|
|
214
|
+
|
|
207
215
|
const doc = await db
|
|
208
216
|
.select()
|
|
209
217
|
.from(documents)
|
|
210
|
-
.where(eq(documents.id,
|
|
218
|
+
.where(eq(documents.id, documentId))
|
|
211
219
|
.get();
|
|
212
220
|
|
|
213
|
-
if (!doc) return err(`Document not found: ${
|
|
221
|
+
if (!doc) return err(`Document not found: ${documentId}`);
|
|
214
222
|
|
|
215
223
|
const updates: Record<string, unknown> = { updatedAt: new Date() };
|
|
216
224
|
|
|
@@ -232,10 +240,10 @@ export function documentTools(ctx: ToolContext) {
|
|
|
232
240
|
await db
|
|
233
241
|
.update(documents)
|
|
234
242
|
.set(updates)
|
|
235
|
-
.where(eq(documents.id,
|
|
243
|
+
.where(eq(documents.id, documentId));
|
|
236
244
|
|
|
237
245
|
if (args.reprocess) {
|
|
238
|
-
processDocument(
|
|
246
|
+
processDocument(documentId).catch(() => {});
|
|
239
247
|
}
|
|
240
248
|
|
|
241
249
|
const updatedFields = [];
|
|
@@ -243,7 +251,7 @@ export function documentTools(ctx: ToolContext) {
|
|
|
243
251
|
if (args.reprocess) updatedFields.push("processingStatus");
|
|
244
252
|
|
|
245
253
|
const result = {
|
|
246
|
-
documentId
|
|
254
|
+
documentId,
|
|
247
255
|
updatedFields,
|
|
248
256
|
processingStatus: args.reprocess ? "queued" : doc.status,
|
|
249
257
|
};
|
|
@@ -264,13 +272,17 @@ export function documentTools(ctx: ToolContext) {
|
|
|
264
272
|
},
|
|
265
273
|
async (args) => {
|
|
266
274
|
try {
|
|
275
|
+
const resolved = await resolveEntityId(documents, documents.id, args.documentId);
|
|
276
|
+
if ("error" in resolved) return err(resolved.error);
|
|
277
|
+
const documentId = resolved.id;
|
|
278
|
+
|
|
267
279
|
const doc = await db
|
|
268
280
|
.select()
|
|
269
281
|
.from(documents)
|
|
270
|
-
.where(eq(documents.id,
|
|
282
|
+
.where(eq(documents.id, documentId))
|
|
271
283
|
.get();
|
|
272
284
|
|
|
273
|
-
if (!doc) return err(`Document not found: ${
|
|
285
|
+
if (!doc) return err(`Document not found: ${documentId}`);
|
|
274
286
|
|
|
275
287
|
// Check task linkage
|
|
276
288
|
if (doc.taskId && !args.cascadeDelete) {
|
|
@@ -285,7 +297,7 @@ export function documentTools(ctx: ToolContext) {
|
|
|
285
297
|
// File may already be deleted
|
|
286
298
|
}
|
|
287
299
|
|
|
288
|
-
await db.delete(documents).where(eq(documents.id,
|
|
300
|
+
await db.delete(documents).where(eq(documents.id, documentId));
|
|
289
301
|
|
|
290
302
|
const result = {
|
|
291
303
|
success: true,
|
|
@@ -308,6 +320,10 @@ export function documentTools(ctx: ToolContext) {
|
|
|
308
320
|
},
|
|
309
321
|
async (args) => {
|
|
310
322
|
try {
|
|
323
|
+
const resolved = await resolveEntityId(documents, documents.id, args.documentId);
|
|
324
|
+
if ("error" in resolved) return err(resolved.error);
|
|
325
|
+
const documentId = resolved.id;
|
|
326
|
+
|
|
311
327
|
const doc = await db
|
|
312
328
|
.select({
|
|
313
329
|
id: documents.id,
|
|
@@ -316,10 +332,10 @@ export function documentTools(ctx: ToolContext) {
|
|
|
316
332
|
extractedText: documents.extractedText,
|
|
317
333
|
})
|
|
318
334
|
.from(documents)
|
|
319
|
-
.where(eq(documents.id,
|
|
335
|
+
.where(eq(documents.id, documentId))
|
|
320
336
|
.get();
|
|
321
337
|
|
|
322
|
-
if (!doc) return err(`Document not found: ${
|
|
338
|
+
if (!doc) return err(`Document not found: ${documentId}`);
|
|
323
339
|
if (doc.status !== "ready")
|
|
324
340
|
return err(`Document not ready (status: ${doc.status}). Wait for preprocessing to complete.`);
|
|
325
341
|
if (!doc.extractedText)
|