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
|
@@ -15,7 +15,12 @@ import {
|
|
|
15
15
|
import { getProfile } from "./profiles/registry";
|
|
16
16
|
import { resolveProfileRuntimePayload, type ResolvedProfileRuntimePayload } from "./profiles/compatibility";
|
|
17
17
|
import type { CanUseToolPolicy } from "./profiles/types";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
buildClaudeSdkEnv,
|
|
20
|
+
CLAUDE_SDK_ALLOWED_TOOLS,
|
|
21
|
+
CLAUDE_SDK_SETTING_SOURCES,
|
|
22
|
+
} from "./runtime/claude-sdk";
|
|
23
|
+
import { getFeaturesForModel } from "@/lib/chat/types";
|
|
19
24
|
import { getActiveLearnedContext } from "./learned-context";
|
|
20
25
|
import { getLaunchCwd, getWorkspaceContext } from "@/lib/environment/workspace-context";
|
|
21
26
|
import { analyzeForLearnedPatterns } from "./pattern-extractor";
|
|
@@ -34,6 +39,97 @@ import {
|
|
|
34
39
|
handleToolPermission,
|
|
35
40
|
clearPermissionCache,
|
|
36
41
|
} from "./tool-permissions";
|
|
42
|
+
import {
|
|
43
|
+
classifyTaskFailureReason,
|
|
44
|
+
toRetryableRuntimeLaunchError,
|
|
45
|
+
type RuntimeLaunchProgress,
|
|
46
|
+
} from "@/lib/agents/runtime/launch-failure";
|
|
47
|
+
|
|
48
|
+
// ─── Stagent MCP injection helpers ──────────────────────────────────────
|
|
49
|
+
//
|
|
50
|
+
// Shared by executeClaudeTask and resumeClaudeTask so the two runtime entry
|
|
51
|
+
// points cannot drift apart. The drift between chat engine injection and
|
|
52
|
+
// claude-code runtime injection is what produced the P0 bug this feature
|
|
53
|
+
// fixes — do not duplicate these patterns inline.
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Merge the in-process stagent MCP server into a profile/browser/external
|
|
57
|
+
* MCP server map. Stagent is spread LAST so no upstream source can shadow
|
|
58
|
+
* the `stagent` key with its own server.
|
|
59
|
+
*
|
|
60
|
+
* `@/lib/chat/stagent-tools` is loaded via dynamic `import()` to avoid a
|
|
61
|
+
* circular-dependency crash: that module transitively pulls in the chat
|
|
62
|
+
* tools registry, which imports the runtime registry (`runtime/catalog`,
|
|
63
|
+
* `runtime/index`), which statically references `claudeRuntimeAdapter` —
|
|
64
|
+
* the very module this file is defined in. A static import here would
|
|
65
|
+
* crash with "Cannot access 'claudeRuntimeAdapter' before initialization"
|
|
66
|
+
* at module-load time. The dynamic import defers the stagent-tools module
|
|
67
|
+
* until `executeClaudeTask` / `resumeClaudeTask` actually run, by which
|
|
68
|
+
* time every module in the graph has finished initializing.
|
|
69
|
+
*/
|
|
70
|
+
async function withStagentMcpServer(
|
|
71
|
+
profileServers: Record<string, unknown>,
|
|
72
|
+
browserServers: Record<string, unknown>,
|
|
73
|
+
externalServers: Record<string, unknown>,
|
|
74
|
+
projectId?: string | null,
|
|
75
|
+
): Promise<Record<string, unknown>> {
|
|
76
|
+
const { createToolServer } = await import("@/lib/chat/stagent-tools");
|
|
77
|
+
const stagentServer = createToolServer(projectId).asMcpServer();
|
|
78
|
+
return {
|
|
79
|
+
...profileServers,
|
|
80
|
+
...browserServers,
|
|
81
|
+
...externalServers,
|
|
82
|
+
stagent: stagentServer,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Prepend `mcp__stagent__*` to a profile's explicit allowedTools so the
|
|
88
|
+
* stagent tool registration survives the SDK preset filter. When the
|
|
89
|
+
* profile has no explicit allowlist and `includeSdkTools` is true, fall
|
|
90
|
+
* back to Phase 1a's CLAUDE_SDK_ALLOWED_TOOLS (Skill, Read/Grep/Glob,
|
|
91
|
+
* Edit/Write/Bash, TodoWrite) so task execution gets the same toolset as
|
|
92
|
+
* chat. Returns `undefined` only when the profile has no allowlist AND
|
|
93
|
+
* the caller does not want SDK tools added — letting the SDK fall
|
|
94
|
+
* through to claude_code preset defaults.
|
|
95
|
+
*/
|
|
96
|
+
function withStagentAllowedTools(
|
|
97
|
+
profileAllowedTools: string[] | undefined,
|
|
98
|
+
includeSdkTools: boolean,
|
|
99
|
+
): string[] | undefined {
|
|
100
|
+
// An empty `allowedTools: []` is treated the same as `undefined` — an
|
|
101
|
+
// empty array is almost never the profile author's intent (they'd get
|
|
102
|
+
// only `mcp__stagent__*` and nothing else). Require at least one tool
|
|
103
|
+
// name for the "profile has explicit list" branch.
|
|
104
|
+
if (profileAllowedTools && profileAllowedTools.length > 0) {
|
|
105
|
+
// Profile has explicit list — respect it. Only prepend stagent.
|
|
106
|
+
return Array.from(new Set(["mcp__stagent__*", ...profileAllowedTools]));
|
|
107
|
+
}
|
|
108
|
+
if (includeSdkTools) {
|
|
109
|
+
// No profile allowlist but runtime has native skills — pass the
|
|
110
|
+
// Phase 1a tool set alongside mcp__stagent__* + browser/external
|
|
111
|
+
// (callers merge their own browser/external patterns into this list).
|
|
112
|
+
return ["mcp__stagent__*", ...CLAUDE_SDK_ALLOWED_TOOLS];
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Write an explicit failure_reason to tasks at terminal-state transitions.
|
|
119
|
+
* Called from handleExecutionError and the execute/resume functions on known
|
|
120
|
+
* error classes. Prefer this over reverse-engineering reasons from text via
|
|
121
|
+
* detectFailureReason in scheduler.ts, which is fragile to SDK message changes.
|
|
122
|
+
*/
|
|
123
|
+
export async function writeTerminalFailureReason(
|
|
124
|
+
taskId: string,
|
|
125
|
+
error: unknown,
|
|
126
|
+
): Promise<void> {
|
|
127
|
+
const reason = classifyTaskFailureReason(error);
|
|
128
|
+
await db
|
|
129
|
+
.update(tasks)
|
|
130
|
+
.set({ failureReason: reason, updatedAt: new Date() })
|
|
131
|
+
.where(eq(tasks.id, taskId));
|
|
132
|
+
}
|
|
37
133
|
|
|
38
134
|
/** Typed representation of messages from the Agent SDK stream */
|
|
39
135
|
interface AgentStreamMessage {
|
|
@@ -104,6 +200,14 @@ export async function finalizeTaskUsage(
|
|
|
104
200
|
startedAt: state.startedAt,
|
|
105
201
|
finishedAt: new Date(),
|
|
106
202
|
});
|
|
203
|
+
|
|
204
|
+
await db
|
|
205
|
+
.update(tasks)
|
|
206
|
+
.set({
|
|
207
|
+
effectiveModelId: state.modelId ?? null,
|
|
208
|
+
updatedAt: new Date(),
|
|
209
|
+
})
|
|
210
|
+
.where(eq(tasks.id, state.taskId));
|
|
107
211
|
}
|
|
108
212
|
|
|
109
213
|
/**
|
|
@@ -116,7 +220,8 @@ async function processAgentStream(
|
|
|
116
220
|
response: AsyncIterable<Record<string, unknown>>,
|
|
117
221
|
abortController: AbortController,
|
|
118
222
|
agentProfileId = "general",
|
|
119
|
-
usageState: TaskUsageState
|
|
223
|
+
usageState: TaskUsageState,
|
|
224
|
+
launchProgress?: RuntimeLaunchProgress
|
|
120
225
|
): Promise<void> {
|
|
121
226
|
let sessionId: string | null = null;
|
|
122
227
|
let receivedResult = false;
|
|
@@ -179,8 +284,14 @@ async function processAgentStream(
|
|
|
179
284
|
// Handle assistant messages (tool use starts)
|
|
180
285
|
if (message.type === "assistant" && message.message?.content) {
|
|
181
286
|
turnCount++;
|
|
287
|
+
if (launchProgress) {
|
|
288
|
+
launchProgress.hasTurnStarted = true;
|
|
289
|
+
}
|
|
182
290
|
for (const block of message.message.content) {
|
|
183
291
|
if (block.type === "tool_use") {
|
|
292
|
+
if (launchProgress) {
|
|
293
|
+
launchProgress.hasToolUse = true;
|
|
294
|
+
}
|
|
184
295
|
// Track screenshot tool_use IDs for result interception
|
|
185
296
|
const toolBlock = block as { type: string; id?: string; name?: string; input?: unknown };
|
|
186
297
|
if (typeof toolBlock.name === "string" && SCREENSHOT_TOOL_NAMES.has(toolBlock.name) && typeof toolBlock.id === "string") {
|
|
@@ -249,6 +360,9 @@ async function processAgentStream(
|
|
|
249
360
|
return;
|
|
250
361
|
}
|
|
251
362
|
receivedResult = true;
|
|
363
|
+
if (launchProgress) {
|
|
364
|
+
launchProgress.hasResult = true;
|
|
365
|
+
}
|
|
252
366
|
const resultText =
|
|
253
367
|
typeof message.result === "string"
|
|
254
368
|
? message.result
|
|
@@ -314,11 +428,14 @@ async function processAgentStream(
|
|
|
314
428
|
? `Agent exhausted its turn limit (${turnCount} turns used) without producing a final result. The task may need fewer sub-queries or a higher maxTurns setting.`
|
|
315
429
|
: "Agent stream ended without producing a result";
|
|
316
430
|
|
|
431
|
+
const streamFailureReason = turnCount > 0 ? "turn_limit_exceeded" : "sdk_error";
|
|
432
|
+
|
|
317
433
|
await db
|
|
318
434
|
.update(tasks)
|
|
319
435
|
.set({
|
|
320
436
|
status: "failed",
|
|
321
437
|
result: errorDetail,
|
|
438
|
+
failureReason: streamFailureReason,
|
|
322
439
|
updatedAt: new Date(),
|
|
323
440
|
})
|
|
324
441
|
.where(eq(tasks.id, taskId));
|
|
@@ -417,6 +534,7 @@ export async function executeClaudeTask(taskId: string): Promise<void> {
|
|
|
417
534
|
const [task] = await db.select().from(tasks).where(eq(tasks.id, taskId));
|
|
418
535
|
if (!task) throw new Error(`Task ${taskId} not found`);
|
|
419
536
|
const usageState = createTaskUsageState(task);
|
|
537
|
+
const launchProgress: RuntimeLaunchProgress = {};
|
|
420
538
|
|
|
421
539
|
const abortController = new AbortController();
|
|
422
540
|
const agentProfileId = task.agentProfile ?? "general";
|
|
@@ -432,13 +550,44 @@ export async function executeClaudeTask(taskId: string): Promise<void> {
|
|
|
432
550
|
await prepareTaskOutputDirectory(taskId, { clearExisting: true });
|
|
433
551
|
const ctx = await buildTaskQueryContext(task, agentProfileId);
|
|
434
552
|
|
|
435
|
-
//
|
|
553
|
+
// Per-schedule override: if the task carries its own maxTurns (set by
|
|
554
|
+
// fireSchedule from schedules.maxTurns), it takes precedence over the
|
|
555
|
+
// profile default. This is the runtime-enforced budget cap.
|
|
556
|
+
const effectiveMaxTurns = task.maxTurns ?? ctx.maxTurns;
|
|
557
|
+
|
|
558
|
+
// Merge browser + external MCP servers, then inject the in-process
|
|
559
|
+
// stagent server via the shared helper (see withStagentMcpServer above).
|
|
560
|
+
// The helper is async because it dynamically imports @/lib/chat/stagent-tools
|
|
561
|
+
// to break a module-load cycle with the runtime registry.
|
|
436
562
|
const [browserServers, externalServers] = await Promise.all([
|
|
437
563
|
getBrowserMcpServers(),
|
|
438
564
|
getExternalMcpServers(),
|
|
439
565
|
]);
|
|
440
|
-
const
|
|
441
|
-
|
|
566
|
+
const mergedMcpServers = await withStagentMcpServer(
|
|
567
|
+
ctx.payload?.mcpServers ?? {},
|
|
568
|
+
browserServers,
|
|
569
|
+
externalServers,
|
|
570
|
+
task.projectId,
|
|
571
|
+
);
|
|
572
|
+
// Capability gate: only pass settingSources + CLAUDE_SDK tools when the
|
|
573
|
+
// runtime is claude-code (or a future runtime with hasNativeSkills).
|
|
574
|
+
// Anthropic-direct and OpenAI-direct task runtimes don't understand
|
|
575
|
+
// these SDK-specific options. Tasks do not carry a model field yet —
|
|
576
|
+
// an empty string falls through to the claude-code default in
|
|
577
|
+
// getFeaturesForModel, so the gate opens by default for the primary
|
|
578
|
+
// claude-code use case. Task 4's resume path follows the same pattern.
|
|
579
|
+
const runtimeFeatures = getFeaturesForModel("");
|
|
580
|
+
const includeSdkNativeTools = runtimeFeatures.hasNativeSkills;
|
|
581
|
+
|
|
582
|
+
// allowedTools merged via shared helper. When the profile has no explicit
|
|
583
|
+
// allowlist AND the runtime has native skills, we fall back to Phase 1a's
|
|
584
|
+
// CLAUDE_SDK_ALLOWED_TOOLS (Skill, Read/Grep/Glob, Edit/Write/Bash,
|
|
585
|
+
// TodoWrite) so task execution matches chat. Computed once so the
|
|
586
|
+
// conditional spread below does not invoke the helper twice.
|
|
587
|
+
const mergedAllowedTools = withStagentAllowedTools(
|
|
588
|
+
ctx.payload?.allowedTools,
|
|
589
|
+
includeSdkNativeTools,
|
|
590
|
+
);
|
|
442
591
|
|
|
443
592
|
const authEnv = await getAuthEnv();
|
|
444
593
|
const response = query({
|
|
@@ -452,11 +601,16 @@ export async function executeClaudeTask(taskId: string): Promise<void> {
|
|
|
452
601
|
systemPrompt: ctx.systemInstructions
|
|
453
602
|
? { type: "preset" as const, preset: "claude_code" as const, append: ctx.systemInstructions }
|
|
454
603
|
: { type: "preset" as const, preset: "claude_code" as const },
|
|
455
|
-
// F9: Bounded turn limit from profile or default
|
|
456
|
-
maxTurns:
|
|
604
|
+
// F9: Bounded turn limit from profile or default; per-schedule override wins
|
|
605
|
+
maxTurns: effectiveMaxTurns,
|
|
457
606
|
// F4: Per-execution budget cap — use task-specific override if set
|
|
458
607
|
maxBudgetUsd: task.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD,
|
|
459
|
-
...(
|
|
608
|
+
...(mergedAllowedTools && { allowedTools: mergedAllowedTools }),
|
|
609
|
+
// Phase 1a parity: load user + project settings (.claude/skills,
|
|
610
|
+
// CLAUDE.md, .claude/rules/*.md) when the runtime supports it.
|
|
611
|
+
...(includeSdkNativeTools && {
|
|
612
|
+
settingSources: [...CLAUDE_SDK_SETTING_SOURCES],
|
|
613
|
+
}),
|
|
460
614
|
...(Object.keys(mergedMcpServers).length > 0 && {
|
|
461
615
|
mcpServers: mergedMcpServers,
|
|
462
616
|
}),
|
|
@@ -476,14 +630,24 @@ export async function executeClaudeTask(taskId: string): Promise<void> {
|
|
|
476
630
|
response as AsyncIterable<Record<string, unknown>>,
|
|
477
631
|
abortController,
|
|
478
632
|
agentProfileId,
|
|
479
|
-
usageState
|
|
633
|
+
usageState,
|
|
634
|
+
launchProgress
|
|
480
635
|
);
|
|
481
636
|
|
|
482
|
-
|
|
483
|
-
|
|
637
|
+
try {
|
|
638
|
+
await analyzeForLearnedPatterns(taskId, agentProfileId);
|
|
639
|
+
} catch (err) {
|
|
484
640
|
console.error("[self-improvement] pattern extraction failed:", err);
|
|
485
|
-
}
|
|
641
|
+
}
|
|
486
642
|
} catch (error: unknown) {
|
|
643
|
+
const retryableLaunchError = toRetryableRuntimeLaunchError({
|
|
644
|
+
runtimeId: "claude-code",
|
|
645
|
+
error,
|
|
646
|
+
progress: launchProgress,
|
|
647
|
+
});
|
|
648
|
+
if (retryableLaunchError) {
|
|
649
|
+
throw retryableLaunchError;
|
|
650
|
+
}
|
|
487
651
|
await handleExecutionError(
|
|
488
652
|
taskId,
|
|
489
653
|
task.title,
|
|
@@ -545,13 +709,37 @@ export async function resumeClaudeTask(taskId: string): Promise<void> {
|
|
|
545
709
|
await prepareTaskOutputDirectory(taskId);
|
|
546
710
|
const ctx = await buildTaskQueryContext(task, profileId);
|
|
547
711
|
|
|
548
|
-
//
|
|
712
|
+
// Per-schedule override: if the task carries its own maxTurns (set by
|
|
713
|
+
// fireSchedule from schedules.maxTurns), it takes precedence over the
|
|
714
|
+
// profile default. This is the runtime-enforced budget cap.
|
|
715
|
+
const effectiveMaxTurns = task.maxTurns ?? ctx.maxTurns;
|
|
716
|
+
|
|
717
|
+
// Merge browser + external MCP servers, then inject the in-process
|
|
718
|
+
// stagent server via the shared helper (see withStagentMcpServer).
|
|
719
|
+
// Async for the same cycle-breaking reason as executeClaudeTask above.
|
|
549
720
|
const [browserServers, externalServers] = await Promise.all([
|
|
550
721
|
getBrowserMcpServers(),
|
|
551
722
|
getExternalMcpServers(),
|
|
552
723
|
]);
|
|
553
|
-
const
|
|
554
|
-
|
|
724
|
+
const mergedMcpServers = await withStagentMcpServer(
|
|
725
|
+
ctx.payload?.mcpServers ?? {},
|
|
726
|
+
browserServers,
|
|
727
|
+
externalServers,
|
|
728
|
+
task.projectId,
|
|
729
|
+
);
|
|
730
|
+
// Capability gate: same logic as executeClaudeTask. Resumed tasks must
|
|
731
|
+
// get the same SDK options as their original run so skills that were
|
|
732
|
+
// visible on first execution remain visible after a resume. `task.model`
|
|
733
|
+
// does not exist on the tasks schema — pass "" which resolves to the
|
|
734
|
+
// claude-code default (hasNativeSkills: true) for every current task
|
|
735
|
+
// flow. See features/task-runtime-skill-parity.md Task 4.
|
|
736
|
+
const runtimeFeatures = getFeaturesForModel("");
|
|
737
|
+
const includeSdkNativeTools = runtimeFeatures.hasNativeSkills;
|
|
738
|
+
|
|
739
|
+
const mergedAllowedTools = withStagentAllowedTools(
|
|
740
|
+
ctx.payload?.allowedTools,
|
|
741
|
+
includeSdkNativeTools,
|
|
742
|
+
);
|
|
555
743
|
|
|
556
744
|
const authEnv = await getAuthEnv();
|
|
557
745
|
const response = query({
|
|
@@ -566,11 +754,15 @@ export async function resumeClaudeTask(taskId: string): Promise<void> {
|
|
|
566
754
|
systemPrompt: ctx.systemInstructions
|
|
567
755
|
? { type: "preset" as const, preset: "claude_code" as const, append: ctx.systemInstructions }
|
|
568
756
|
: { type: "preset" as const, preset: "claude_code" as const },
|
|
569
|
-
// F9: Bounded turn limit from profile or default
|
|
570
|
-
maxTurns:
|
|
757
|
+
// F9: Bounded turn limit from profile or default; per-schedule override wins
|
|
758
|
+
maxTurns: effectiveMaxTurns,
|
|
571
759
|
// F4: Per-execution budget cap — use task-specific override if set
|
|
572
760
|
maxBudgetUsd: task.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD,
|
|
573
|
-
...(
|
|
761
|
+
...(mergedAllowedTools && { allowedTools: mergedAllowedTools }),
|
|
762
|
+
// Phase 1a parity: match executeClaudeTask — see Task 3 rationale.
|
|
763
|
+
...(includeSdkNativeTools && {
|
|
764
|
+
settingSources: [...CLAUDE_SDK_SETTING_SOURCES],
|
|
765
|
+
}),
|
|
574
766
|
...(Object.keys(mergedMcpServers).length > 0 && {
|
|
575
767
|
mcpServers: mergedMcpServers,
|
|
576
768
|
}),
|
|
@@ -593,10 +785,11 @@ export async function resumeClaudeTask(taskId: string): Promise<void> {
|
|
|
593
785
|
usageState
|
|
594
786
|
);
|
|
595
787
|
|
|
596
|
-
|
|
597
|
-
|
|
788
|
+
try {
|
|
789
|
+
await analyzeForLearnedPatterns(taskId, profileId);
|
|
790
|
+
} catch (err) {
|
|
598
791
|
console.error("[self-improvement] pattern extraction failed:", err);
|
|
599
|
-
}
|
|
792
|
+
}
|
|
600
793
|
} catch (error: unknown) {
|
|
601
794
|
const errorMessage =
|
|
602
795
|
error instanceof Error ? error.message : String(error);
|
|
@@ -612,6 +805,7 @@ export async function resumeClaudeTask(taskId: string): Promise<void> {
|
|
|
612
805
|
status: "failed",
|
|
613
806
|
result: "Session expired — re-queue for fresh start",
|
|
614
807
|
sessionId: null,
|
|
808
|
+
failureReason: "auth_failed",
|
|
615
809
|
updatedAt: new Date(),
|
|
616
810
|
})
|
|
617
811
|
.where(eq(tasks.id, taskId));
|
|
@@ -667,11 +861,13 @@ async function handleExecutionError(
|
|
|
667
861
|
return;
|
|
668
862
|
}
|
|
669
863
|
|
|
864
|
+
const failureReason = classifyTaskFailureReason(error);
|
|
670
865
|
await db
|
|
671
866
|
.update(tasks)
|
|
672
867
|
.set({
|
|
673
868
|
status: "failed",
|
|
674
869
|
result: errorMessage,
|
|
870
|
+
failureReason,
|
|
675
871
|
updatedAt: new Date(),
|
|
676
872
|
})
|
|
677
873
|
.where(eq(tasks.id, taskId));
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { licenseManager } from "@/lib/license/manager";
|
|
2
|
-
import { createTierLimitNotification } from "@/lib/license/notifications";
|
|
3
|
-
|
|
4
1
|
interface RunningExecution {
|
|
5
2
|
abortController: AbortController;
|
|
6
3
|
sessionId: string | null;
|
|
@@ -17,42 +14,10 @@ export function getExecution(taskId: string): RunningExecution | undefined {
|
|
|
17
14
|
return executions.get(taskId);
|
|
18
15
|
}
|
|
19
16
|
|
|
20
|
-
/**
|
|
21
|
-
* Register a running execution. Checks the parallel workflow limit
|
|
22
|
-
* for the current tier before allowing the execution to proceed.
|
|
23
|
-
*
|
|
24
|
-
* @throws {ParallelLimitError} if the concurrent execution limit is reached
|
|
25
|
-
*/
|
|
26
17
|
export function setExecution(taskId: string, execution: RunningExecution): void {
|
|
27
|
-
const limit = licenseManager.getLimit("parallelWorkflows");
|
|
28
|
-
const currentCount = executions.size;
|
|
29
|
-
|
|
30
|
-
if (Number.isFinite(limit) && currentCount >= limit) {
|
|
31
|
-
const tier = licenseManager.getTier();
|
|
32
|
-
// Fire-and-forget notification
|
|
33
|
-
createTierLimitNotification("parallelWorkflows", currentCount, limit, taskId).catch(() => {});
|
|
34
|
-
throw new ParallelLimitError(currentCount, limit, tier);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
18
|
executions.set(taskId, execution);
|
|
38
19
|
}
|
|
39
20
|
|
|
40
|
-
export class ParallelLimitError extends Error {
|
|
41
|
-
public readonly current: number;
|
|
42
|
-
public readonly limit: number;
|
|
43
|
-
public readonly tier: string;
|
|
44
|
-
|
|
45
|
-
constructor(current: number, limit: number, tier: string) {
|
|
46
|
-
super(
|
|
47
|
-
`Parallel workflow limit reached (${current}/${limit}) on ${tier} tier. Wait for a running task to complete or upgrade.`
|
|
48
|
-
);
|
|
49
|
-
this.name = "ParallelLimitError";
|
|
50
|
-
this.current = current;
|
|
51
|
-
this.limit = limit;
|
|
52
|
-
this.tier = tier;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
21
|
export function removeExecution(taskId: string): void {
|
|
57
22
|
executions.delete(taskId);
|
|
58
23
|
}
|
|
@@ -125,8 +125,8 @@ export async function processHandoffs(): Promise<void> {
|
|
|
125
125
|
|
|
126
126
|
// Fire-and-forget task execution
|
|
127
127
|
try {
|
|
128
|
-
const {
|
|
129
|
-
|
|
128
|
+
const { startTaskExecution } = await import("@/lib/agents/task-dispatch");
|
|
129
|
+
startTaskExecution(taskId).catch((err) => {
|
|
130
130
|
console.error(`[handoff] task execution failed for message ${msg.id}:`, err);
|
|
131
131
|
});
|
|
132
132
|
} catch (err) {
|
|
@@ -5,9 +5,6 @@ import type { LearnedContextRow } from "@/lib/db/schema";
|
|
|
5
5
|
import { runMetaCompletion } from "./runtime/claude";
|
|
6
6
|
import { getSettingSync } from "@/lib/settings/helpers";
|
|
7
7
|
import { SETTINGS_KEYS } from "@/lib/constants/settings";
|
|
8
|
-
import { checkLimit } from "@/lib/license/limit-check";
|
|
9
|
-
import { getContextVersionCount } from "@/lib/license/limit-queries";
|
|
10
|
-
import { createTierLimitNotification } from "@/lib/license/notifications";
|
|
11
8
|
|
|
12
9
|
const DEFAULT_CONTEXT_CHAR_LIMIT = 8_000;
|
|
13
10
|
const SUMMARIZATION_RATIO = 0.75;
|
|
@@ -95,15 +92,6 @@ export async function proposeContextAddition(
|
|
|
95
92
|
additions: string,
|
|
96
93
|
options?: { silent?: boolean }
|
|
97
94
|
): Promise<string> {
|
|
98
|
-
// Tier limit check — context version cap per profile
|
|
99
|
-
const versionCount = getContextVersionCount(profileId);
|
|
100
|
-
const limitResult = checkLimit("contextVersions", versionCount);
|
|
101
|
-
if (!limitResult.allowed) {
|
|
102
|
-
createTierLimitNotification("contextVersions", versionCount, limitResult.limit, taskId).catch(() => {});
|
|
103
|
-
throw new Error(
|
|
104
|
-
`Context version limit reached (${versionCount}/${limitResult.limit}). Upgrade to unlock more capacity.`
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
95
|
|
|
108
96
|
const version = getNextVersion(profileId);
|
|
109
97
|
const notificationId = options?.silent ? null : crypto.randomUUID();
|
|
@@ -181,6 +181,7 @@ export async function batchApproveProposals(
|
|
|
181
181
|
await import("./learned-context");
|
|
182
182
|
|
|
183
183
|
let approved = 0;
|
|
184
|
+
const touchedProfileIds = new Set<string>();
|
|
184
185
|
for (const rowId of proposalRowIds) {
|
|
185
186
|
const [row] = db
|
|
186
187
|
.select()
|
|
@@ -229,16 +230,28 @@ export async function batchApproveProposals(
|
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
approved++;
|
|
232
|
-
|
|
233
|
-
const sizeInfo = checkContextSize(row.profileId);
|
|
234
|
-
if (sizeInfo.needsSummarization) {
|
|
235
|
-
await summarizeContext(row.profileId);
|
|
236
|
-
}
|
|
233
|
+
touchedProfileIds.add(row.profileId);
|
|
237
234
|
}
|
|
238
235
|
|
|
239
236
|
// Mark the batch notification as responded
|
|
240
237
|
await markBatchNotificationResponded(proposalRowIds, "approved");
|
|
241
238
|
|
|
239
|
+
const profilesNeedingSummarization = [...touchedProfileIds].filter(
|
|
240
|
+
(profileId) => checkContextSize(profileId).needsSummarization
|
|
241
|
+
);
|
|
242
|
+
void Promise.allSettled(
|
|
243
|
+
profilesNeedingSummarization.map(async (profileId) => {
|
|
244
|
+
try {
|
|
245
|
+
await summarizeContext(profileId);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.error(
|
|
248
|
+
"[learning-session] Failed to summarize approved context batch:",
|
|
249
|
+
error
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
);
|
|
254
|
+
|
|
242
255
|
return approved;
|
|
243
256
|
}
|
|
244
257
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "fs";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { listFusedProfiles } from "@/lib/agents/profiles/list-fused-profiles";
|
|
6
|
+
|
|
7
|
+
describe("listFusedProfiles", () => {
|
|
8
|
+
let projectDir: string;
|
|
9
|
+
let userSkillsDir: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
projectDir = mkdtempSync(join(tmpdir(), "stagent-skills-"));
|
|
13
|
+
userSkillsDir = mkdtempSync(join(tmpdir(), "stagent-user-skills-"));
|
|
14
|
+
mkdirSync(join(projectDir, ".claude", "skills"), { recursive: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
19
|
+
rmSync(userSkillsDir, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function writeSkill(baseDir: string, name: string, frontmatter: string) {
|
|
23
|
+
mkdirSync(join(baseDir, name), { recursive: true });
|
|
24
|
+
writeFileSync(
|
|
25
|
+
join(baseDir, name, "SKILL.md"),
|
|
26
|
+
`---\n${frontmatter}\n---\n\nbody for ${name}\n`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
it("returns registry profiles when no filesystem skills exist", async () => {
|
|
31
|
+
const result = await listFusedProfiles(projectDir, userSkillsDir);
|
|
32
|
+
// Should contain at least one registry profile (builtin)
|
|
33
|
+
expect(result.length).toBeGreaterThan(0);
|
|
34
|
+
expect(result.every((p) => typeof p.id === "string")).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("surfaces a project .claude/skills/<name> entry", async () => {
|
|
38
|
+
writeSkill(
|
|
39
|
+
join(projectDir, ".claude", "skills"),
|
|
40
|
+
"my-project-skill",
|
|
41
|
+
`name: my-project-skill\ndescription: Test project skill`
|
|
42
|
+
);
|
|
43
|
+
const result = await listFusedProfiles(projectDir, userSkillsDir);
|
|
44
|
+
expect(result.some((p) => p.id === "my-project-skill")).toBe(true);
|
|
45
|
+
const skill = result.find((p) => p.id === "my-project-skill")!;
|
|
46
|
+
expect(skill.name).toBe("my-project-skill");
|
|
47
|
+
expect(skill.description).toBe("Test project skill");
|
|
48
|
+
expect(skill.origin).toBe("filesystem-project");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("sets projectDir to the project root (not the skills subdirectory) on filesystem-project entries", async () => {
|
|
52
|
+
writeSkill(
|
|
53
|
+
join(projectDir, ".claude", "skills"),
|
|
54
|
+
"my-scoped-skill",
|
|
55
|
+
`name: my-scoped-skill\ndescription: Scoped`
|
|
56
|
+
);
|
|
57
|
+
const result = await listFusedProfiles(projectDir, userSkillsDir);
|
|
58
|
+
const skill = result.find((p) => p.id === "my-scoped-skill")!;
|
|
59
|
+
expect(skill.projectDir).toBe(projectDir);
|
|
60
|
+
// Negative: must not be the .claude/skills subdirectory
|
|
61
|
+
expect(skill.projectDir).not.toContain(".claude/skills");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("surfaces a user ~/.claude/skills/<name> entry", async () => {
|
|
65
|
+
writeSkill(
|
|
66
|
+
userSkillsDir,
|
|
67
|
+
"my-user-skill",
|
|
68
|
+
`name: my-user-skill\ndescription: Test user skill`
|
|
69
|
+
);
|
|
70
|
+
const result = await listFusedProfiles(projectDir, userSkillsDir);
|
|
71
|
+
expect(result.some((p) => p.id === "my-user-skill")).toBe(true);
|
|
72
|
+
expect(
|
|
73
|
+
result.find((p) => p.id === "my-user-skill")!.origin
|
|
74
|
+
).toBe("filesystem-user");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("dedupes by id — registry profile wins over filesystem skill with same id", async () => {
|
|
78
|
+
// "general" is a known builtin registry profile id; write a filesystem
|
|
79
|
+
// skill with the same id to force a collision.
|
|
80
|
+
writeSkill(
|
|
81
|
+
join(projectDir, ".claude", "skills"),
|
|
82
|
+
"general",
|
|
83
|
+
`name: general\ndescription: This should be overridden by registry`
|
|
84
|
+
);
|
|
85
|
+
const result = await listFusedProfiles(projectDir, userSkillsDir);
|
|
86
|
+
const entries = result.filter((p) => p.id === "general");
|
|
87
|
+
expect(entries).toHaveLength(1);
|
|
88
|
+
// Registry description should win (not the filesystem-overridden one)
|
|
89
|
+
expect(entries[0].description).not.toBe("This should be overridden by registry");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("logs and skips a malformed SKILL.md (no name field in frontmatter)", async () => {
|
|
93
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
94
|
+
writeSkill(
|
|
95
|
+
join(projectDir, ".claude", "skills"),
|
|
96
|
+
"broken-skill",
|
|
97
|
+
`description: Missing name field — broken`
|
|
98
|
+
);
|
|
99
|
+
const result = await listFusedProfiles(projectDir, userSkillsDir);
|
|
100
|
+
expect(result.some((p) => p.id === "broken-skill")).toBe(false);
|
|
101
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
102
|
+
warnSpy.mockRestore();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("returns an empty-safe result when projectDir does not exist", async () => {
|
|
106
|
+
const result = await listFusedProfiles("/nonexistent/path", userSkillsDir);
|
|
107
|
+
// Should still return registry + user skills, no throw
|
|
108
|
+
expect(Array.isArray(result)).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
});
|