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.
Files changed (396) hide show
  1. package/README.md +20 -44
  2. package/dist/cli.js +66 -18
  3. package/docs/.coverage-gaps.json +144 -56
  4. package/docs/.last-generated +1 -1
  5. package/docs/features/agent-intelligence.md +12 -2
  6. package/docs/features/chat.md +40 -5
  7. package/docs/features/cost-usage.md +1 -1
  8. package/docs/features/documents.md +5 -2
  9. package/docs/features/inbox-notifications.md +10 -2
  10. package/docs/features/keyboard-navigation.md +12 -3
  11. package/docs/features/provider-runtimes.md +20 -2
  12. package/docs/features/schedules.md +32 -4
  13. package/docs/features/settings.md +28 -5
  14. package/docs/features/shared-components.md +7 -3
  15. package/docs/features/tables.md +11 -2
  16. package/docs/features/tool-permissions.md +6 -2
  17. package/docs/features/workflows.md +14 -4
  18. package/docs/index.md +1 -1
  19. package/docs/journeys/developer.md +39 -2
  20. package/docs/journeys/personal-use.md +32 -8
  21. package/docs/journeys/power-user.md +45 -14
  22. package/docs/journeys/work-use.md +17 -8
  23. package/docs/manifest.json +15 -15
  24. package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
  25. package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
  26. package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
  27. package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
  28. package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
  29. package/docs/superpowers/plans/2026-04-14-chat-command-namespace-refactor.md +1390 -0
  30. package/docs/superpowers/plans/2026-04-14-chat-environment-integration.md +1561 -0
  31. package/docs/superpowers/plans/2026-04-14-chat-polish-bundle-v1.md +1219 -0
  32. package/docs/superpowers/plans/2026-04-14-chat-session-persistence-provider-closeout.md +399 -0
  33. package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
  34. package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
  35. package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
  36. package/next.config.mjs +1 -0
  37. package/package.json +3 -2
  38. package/src/__tests__/instrumentation-smoke.test.ts +15 -0
  39. package/src/app/analytics/page.tsx +1 -21
  40. package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
  41. package/src/app/api/chat/conversations/[id]/skills/__tests__/activate.test.ts +141 -0
  42. package/src/app/api/chat/conversations/[id]/skills/activate/route.ts +74 -0
  43. package/src/app/api/chat/conversations/[id]/skills/deactivate/route.ts +33 -0
  44. package/src/app/api/chat/export/route.ts +52 -0
  45. package/src/app/api/chat/files/search/route.ts +50 -0
  46. package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
  47. package/src/app/api/environment/rescan-if-stale/__tests__/route.test.ts +45 -0
  48. package/src/app/api/environment/rescan-if-stale/route.ts +23 -0
  49. package/src/app/api/environment/skills/route.ts +13 -0
  50. package/src/app/api/instance/config/route.ts +41 -0
  51. package/src/app/api/instance/init/route.ts +34 -0
  52. package/src/app/api/instance/upgrade/check/route.ts +26 -0
  53. package/src/app/api/instance/upgrade/route.ts +96 -0
  54. package/src/app/api/instance/upgrade/status/route.ts +35 -0
  55. package/src/app/api/memory/route.ts +0 -11
  56. package/src/app/api/notifications/route.ts +4 -2
  57. package/src/app/api/projects/[id]/route.ts +5 -155
  58. package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
  59. package/src/app/api/schedules/[id]/execute/route.ts +111 -0
  60. package/src/app/api/schedules/[id]/route.ts +9 -1
  61. package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
  62. package/src/app/api/schedules/route.ts +3 -12
  63. package/src/app/api/settings/chat/pins/route.ts +94 -0
  64. package/src/app/api/settings/chat/saved-searches/__tests__/route.test.ts +119 -0
  65. package/src/app/api/settings/chat/saved-searches/route.ts +79 -0
  66. package/src/app/api/settings/environment/route.ts +26 -0
  67. package/src/app/api/settings/openai/login/route.ts +22 -0
  68. package/src/app/api/settings/openai/logout/route.ts +7 -0
  69. package/src/app/api/settings/openai/route.ts +21 -1
  70. package/src/app/api/settings/providers/route.ts +35 -8
  71. package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
  72. package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
  73. package/src/app/api/tables/[id]/enrich/route.ts +147 -0
  74. package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
  75. package/src/app/api/tasks/[id]/execute/route.ts +52 -33
  76. package/src/app/api/tasks/[id]/respond/route.ts +31 -15
  77. package/src/app/api/tasks/[id]/resume/route.ts +24 -3
  78. package/src/app/api/workflows/[id]/resume/route.ts +59 -0
  79. package/src/app/api/workflows/[id]/status/route.ts +22 -8
  80. package/src/app/api/workspace/context/route.ts +2 -0
  81. package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
  82. package/src/app/chat/page.tsx +11 -0
  83. package/src/app/documents/page.tsx +4 -1
  84. package/src/app/inbox/page.tsx +12 -5
  85. package/src/app/layout.tsx +42 -21
  86. package/src/app/page.tsx +0 -2
  87. package/src/app/settings/page.tsx +8 -9
  88. package/src/components/chat/__tests__/capability-banner.test.tsx +38 -0
  89. package/src/components/chat/__tests__/chat-session-provider.test.tsx +573 -0
  90. package/src/components/chat/__tests__/skill-row.test.tsx +91 -0
  91. package/src/components/chat/capability-banner.tsx +68 -0
  92. package/src/components/chat/chat-command-popover.tsx +670 -49
  93. package/src/components/chat/chat-input.tsx +104 -10
  94. package/src/components/chat/chat-message.tsx +12 -3
  95. package/src/components/chat/chat-session-provider.tsx +790 -0
  96. package/src/components/chat/chat-shell.tsx +151 -401
  97. package/src/components/chat/command-tab-bar.tsx +68 -0
  98. package/src/components/chat/conversation-template-picker.tsx +421 -0
  99. package/src/components/chat/help-dialog.tsx +39 -0
  100. package/src/components/chat/skill-composition-conflict-dialog.tsx +96 -0
  101. package/src/components/chat/skill-row.tsx +147 -0
  102. package/src/components/documents/document-browser.tsx +37 -19
  103. package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
  104. package/src/components/instance/instance-section.tsx +382 -0
  105. package/src/components/instance/upgrade-badge.tsx +219 -0
  106. package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
  107. package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
  108. package/src/components/notifications/__tests__/permission-response-actions.test.tsx +70 -0
  109. package/src/components/notifications/batch-proposal-review.tsx +20 -5
  110. package/src/components/notifications/inbox-list.tsx +11 -2
  111. package/src/components/notifications/notification-item.tsx +56 -2
  112. package/src/components/notifications/pending-approval-host.tsx +56 -37
  113. package/src/components/notifications/permission-response-actions.tsx +155 -1
  114. package/src/components/schedules/schedule-create-sheet.tsx +19 -1
  115. package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
  116. package/src/components/schedules/schedule-form.tsx +31 -0
  117. package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
  118. package/src/components/settings/auth-method-selector.tsx +19 -4
  119. package/src/components/settings/auth-status-badge.tsx +28 -3
  120. package/src/components/settings/environment-section.tsx +102 -0
  121. package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
  122. package/src/components/settings/openai-runtime-section.tsx +7 -1
  123. package/src/components/settings/providers-runtimes-section.tsx +138 -19
  124. package/src/components/shared/__tests__/filter-hint.test.tsx +40 -0
  125. package/src/components/shared/__tests__/saved-searches-manager.test.tsx +147 -0
  126. package/src/components/shared/app-sidebar.tsx +4 -3
  127. package/src/components/shared/command-palette.tsx +266 -7
  128. package/src/components/shared/filter-hint.tsx +70 -0
  129. package/src/components/shared/filter-input.tsx +59 -0
  130. package/src/components/shared/saved-searches-manager.tsx +199 -0
  131. package/src/components/shared/theme-toggle.tsx +5 -24
  132. package/src/components/shared/workspace-indicator.tsx +61 -2
  133. package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
  134. package/src/components/tables/table-create-sheet.tsx +4 -0
  135. package/src/components/tables/table-enrichment-runs.tsx +103 -0
  136. package/src/components/tables/table-enrichment-sheet.tsx +538 -0
  137. package/src/components/tables/table-spreadsheet.tsx +29 -5
  138. package/src/components/tables/table-toolbar.tsx +10 -1
  139. package/src/components/tasks/kanban-board.tsx +1 -0
  140. package/src/components/tasks/kanban-column.tsx +53 -14
  141. package/src/components/tasks/task-bento-grid.tsx +31 -2
  142. package/src/components/tasks/task-card.tsx +29 -3
  143. package/src/components/tasks/task-chip-bar.tsx +54 -1
  144. package/src/components/tasks/task-result-renderer.tsx +1 -1
  145. package/src/components/workflows/delay-step-body.tsx +109 -0
  146. package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
  147. package/src/components/workflows/loop-status-view.tsx +1 -1
  148. package/src/components/workflows/shared/step-result.tsx +78 -0
  149. package/src/components/workflows/shared/workflow-header.tsx +141 -0
  150. package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
  151. package/src/components/workflows/swarm-dashboard.tsx +2 -15
  152. package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
  153. package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
  154. package/src/components/workflows/workflow-form-view.tsx +133 -16
  155. package/src/components/workflows/workflow-status-view.tsx +30 -740
  156. package/src/hooks/__tests__/use-chat-autocomplete-tabs.test.ts +47 -0
  157. package/src/hooks/__tests__/use-saved-searches.test.ts +70 -0
  158. package/src/hooks/use-active-skills.ts +110 -0
  159. package/src/hooks/use-chat-autocomplete.ts +120 -7
  160. package/src/hooks/use-enriched-skills.ts +19 -0
  161. package/src/hooks/use-pinned-entries.ts +104 -0
  162. package/src/hooks/use-recent-user-messages.ts +19 -0
  163. package/src/hooks/use-saved-searches.ts +142 -0
  164. package/src/instrumentation-node.ts +94 -0
  165. package/src/instrumentation.ts +4 -48
  166. package/src/lib/agents/__tests__/claude-agent-sdk-options.test.ts +56 -0
  167. package/src/lib/agents/__tests__/claude-agent.test.ts +212 -0
  168. package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
  169. package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
  170. package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
  171. package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
  172. package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
  173. package/src/lib/agents/__tests__/task-dispatch.test.ts +166 -0
  174. package/src/lib/agents/__tests__/tool-permissions.test.ts +60 -0
  175. package/src/lib/agents/claude-agent.ts +217 -21
  176. package/src/lib/agents/execution-manager.ts +0 -35
  177. package/src/lib/agents/handoff/bus.ts +2 -2
  178. package/src/lib/agents/learned-context.ts +0 -12
  179. package/src/lib/agents/learning-session.ts +18 -5
  180. package/src/lib/agents/profiles/__tests__/list-fused-profiles.test.ts +110 -0
  181. package/src/lib/agents/profiles/__tests__/registry.test.ts +53 -4
  182. package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +97 -0
  183. package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +36 -0
  184. package/src/lib/agents/profiles/list-fused-profiles.ts +104 -0
  185. package/src/lib/agents/profiles/registry.ts +18 -0
  186. package/src/lib/agents/profiles/types.ts +7 -1
  187. package/src/lib/agents/router.ts +3 -6
  188. package/src/lib/agents/runtime/__tests__/catalog.test.ts +130 -0
  189. package/src/lib/agents/runtime/__tests__/execution-target.test.ts +183 -0
  190. package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
  191. package/src/lib/agents/runtime/anthropic-direct.ts +8 -0
  192. package/src/lib/agents/runtime/catalog.ts +121 -0
  193. package/src/lib/agents/runtime/claude-sdk.ts +32 -0
  194. package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
  195. package/src/lib/agents/runtime/execution-target.ts +456 -0
  196. package/src/lib/agents/runtime/index.ts +4 -0
  197. package/src/lib/agents/runtime/launch-failure.ts +101 -0
  198. package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
  199. package/src/lib/agents/runtime/openai-codex.ts +64 -60
  200. package/src/lib/agents/runtime/openai-direct.ts +8 -0
  201. package/src/lib/agents/runtime/types.ts +8 -0
  202. package/src/lib/agents/task-dispatch.ts +220 -0
  203. package/src/lib/agents/tool-permissions.ts +16 -1
  204. package/src/lib/book/chapter-mapping.ts +11 -0
  205. package/src/lib/book/content.ts +10 -0
  206. package/src/lib/chat/__tests__/active-skill-injection.test.ts +261 -0
  207. package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
  208. package/src/lib/chat/__tests__/clean-filter-input.test.ts +68 -0
  209. package/src/lib/chat/__tests__/command-tabs.test.ts +68 -0
  210. package/src/lib/chat/__tests__/context-builder-files.test.ts +112 -0
  211. package/src/lib/chat/__tests__/dismissals.test.ts +65 -0
  212. package/src/lib/chat/__tests__/engine-sdk-options.test.ts +117 -0
  213. package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
  214. package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
  215. package/src/lib/chat/__tests__/skill-conflict.test.ts +35 -0
  216. package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
  217. package/src/lib/chat/__tests__/types.test.ts +28 -0
  218. package/src/lib/chat/active-skills.ts +31 -0
  219. package/src/lib/chat/active-streams.ts +27 -0
  220. package/src/lib/chat/clean-filter-input.ts +30 -0
  221. package/src/lib/chat/codex-engine.ts +46 -24
  222. package/src/lib/chat/command-tabs.ts +61 -0
  223. package/src/lib/chat/context-builder.ts +146 -4
  224. package/src/lib/chat/dismissals.ts +73 -0
  225. package/src/lib/chat/engine.ts +159 -18
  226. package/src/lib/chat/files/__tests__/search.test.ts +135 -0
  227. package/src/lib/chat/files/expand-mention.ts +76 -0
  228. package/src/lib/chat/files/search.ts +99 -0
  229. package/src/lib/chat/reconcile.ts +117 -0
  230. package/src/lib/chat/skill-composition.ts +210 -0
  231. package/src/lib/chat/skill-conflict.ts +105 -0
  232. package/src/lib/chat/stagent-tools.ts +7 -19
  233. package/src/lib/chat/stream-telemetry.ts +137 -0
  234. package/src/lib/chat/suggested-prompts.ts +28 -1
  235. package/src/lib/chat/system-prompt.ts +48 -1
  236. package/src/lib/chat/tool-catalog.ts +35 -4
  237. package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
  238. package/src/lib/chat/tools/__tests__/profile-tools.test.ts +51 -0
  239. package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
  240. package/src/lib/chat/tools/__tests__/settings-tools.test.ts +294 -0
  241. package/src/lib/chat/tools/__tests__/skill-tools.test.ts +474 -0
  242. package/src/lib/chat/tools/__tests__/task-tools.test.ts +399 -0
  243. package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +351 -0
  244. package/src/lib/chat/tools/blueprint-tools.ts +190 -0
  245. package/src/lib/chat/tools/document-tools.ts +29 -13
  246. package/src/lib/chat/tools/helpers.ts +41 -0
  247. package/src/lib/chat/tools/notification-tools.ts +9 -5
  248. package/src/lib/chat/tools/profile-tools.ts +120 -23
  249. package/src/lib/chat/tools/project-tools.ts +33 -0
  250. package/src/lib/chat/tools/schedule-tools.ts +44 -11
  251. package/src/lib/chat/tools/skill-tools.ts +183 -0
  252. package/src/lib/chat/tools/table-tools.ts +71 -0
  253. package/src/lib/chat/tools/task-tools.ts +89 -21
  254. package/src/lib/chat/tools/workflow-tools.ts +275 -32
  255. package/src/lib/chat/types.ts +15 -0
  256. package/src/lib/constants/settings.ts +10 -18
  257. package/src/lib/data/__tests__/clear.test.ts +56 -2
  258. package/src/lib/data/clear.ts +17 -16
  259. package/src/lib/data/delete-project.ts +171 -0
  260. package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
  261. package/src/lib/db/bootstrap.ts +62 -16
  262. package/src/lib/db/index.ts +5 -0
  263. package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
  264. package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
  265. package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
  266. package/src/lib/db/migrations/0026_drop_license.sql +3 -0
  267. package/src/lib/db/migrations/meta/_journal.json +21 -0
  268. package/src/lib/db/schema.ts +94 -23
  269. package/src/lib/environment/__tests__/auto-promote.test.ts +132 -0
  270. package/src/lib/environment/__tests__/list-skills-enriched.test.ts +55 -0
  271. package/src/lib/environment/__tests__/skill-enrichment.test.ts +129 -0
  272. package/src/lib/environment/__tests__/skill-recommendations.test.ts +87 -0
  273. package/src/lib/environment/data.ts +9 -0
  274. package/src/lib/environment/list-skills.ts +176 -0
  275. package/src/lib/environment/parsers/__tests__/skill.test.ts +54 -0
  276. package/src/lib/environment/parsers/skill.ts +26 -5
  277. package/src/lib/environment/profile-generator.ts +54 -0
  278. package/src/lib/environment/skill-enrichment.ts +106 -0
  279. package/src/lib/environment/skill-recommendations.ts +66 -0
  280. package/src/lib/environment/workspace-context.ts +13 -1
  281. package/src/lib/filters/__tests__/parse.quoted.test.ts +40 -0
  282. package/src/lib/filters/__tests__/parse.test.ts +135 -0
  283. package/src/lib/filters/parse.ts +86 -0
  284. package/src/lib/import/dedup.ts +4 -54
  285. package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
  286. package/src/lib/instance/__tests__/detect.test.ts +115 -0
  287. package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
  288. package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
  289. package/src/lib/instance/__tests__/settings.test.ts +83 -0
  290. package/src/lib/instance/__tests__/upgrade-poller.test.ts +181 -0
  291. package/src/lib/instance/bootstrap.ts +270 -0
  292. package/src/lib/instance/detect.ts +49 -0
  293. package/src/lib/instance/fingerprint.ts +76 -0
  294. package/src/lib/instance/git-ops.ts +95 -0
  295. package/src/lib/instance/settings.ts +61 -0
  296. package/src/lib/instance/types.ts +77 -0
  297. package/src/lib/instance/upgrade-poller.ts +205 -0
  298. package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
  299. package/src/lib/notifications/visibility.ts +33 -0
  300. package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
  301. package/src/lib/schedules/__tests__/config.test.ts +62 -0
  302. package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
  303. package/src/lib/schedules/__tests__/integration.test.ts +82 -0
  304. package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
  305. package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
  306. package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
  307. package/src/lib/schedules/collision-check.ts +105 -0
  308. package/src/lib/schedules/config.ts +53 -0
  309. package/src/lib/schedules/scheduler.ts +236 -17
  310. package/src/lib/schedules/slot-claim.ts +105 -0
  311. package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
  312. package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
  313. package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
  314. package/src/lib/settings/openai-auth.ts +105 -10
  315. package/src/lib/settings/openai-login-manager.ts +260 -0
  316. package/src/lib/settings/runtime-setup.ts +14 -4
  317. package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
  318. package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
  319. package/src/lib/tables/enrichment-planner.ts +454 -0
  320. package/src/lib/tables/enrichment.ts +328 -0
  321. package/src/lib/tables/query-builder.ts +5 -2
  322. package/src/lib/tables/trigger-evaluator.ts +3 -2
  323. package/src/lib/theme.ts +71 -0
  324. package/src/lib/usage/ledger.ts +2 -18
  325. package/src/lib/util/__tests__/similarity.test.ts +106 -0
  326. package/src/lib/util/similarity.ts +77 -0
  327. package/src/lib/utils/format-timestamp.ts +24 -0
  328. package/src/lib/utils/stagent-paths.ts +12 -0
  329. package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
  330. package/src/lib/validators/__tests__/settings.test.ts +10 -0
  331. package/src/lib/validators/blueprint.ts +70 -9
  332. package/src/lib/validators/profile.ts +2 -2
  333. package/src/lib/validators/settings.ts +3 -1
  334. package/src/lib/workflows/__tests__/delay.test.ts +196 -0
  335. package/src/lib/workflows/__tests__/engine.test.ts +8 -0
  336. package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
  337. package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
  338. package/src/lib/workflows/blueprints/__tests__/render-prompt.test.ts +124 -0
  339. package/src/lib/workflows/blueprints/instantiator.ts +22 -1
  340. package/src/lib/workflows/blueprints/render-prompt.ts +71 -0
  341. package/src/lib/workflows/blueprints/types.ts +16 -2
  342. package/src/lib/workflows/delay.ts +106 -0
  343. package/src/lib/workflows/engine.ts +212 -7
  344. package/src/lib/workflows/loop-executor.ts +349 -24
  345. package/src/lib/workflows/post-action.ts +91 -0
  346. package/src/lib/workflows/types.ts +166 -1
  347. package/src/test/setup.ts +10 -0
  348. package/src/app/api/license/checkout/route.ts +0 -28
  349. package/src/app/api/license/portal/route.ts +0 -26
  350. package/src/app/api/license/route.ts +0 -89
  351. package/src/app/api/license/usage/route.ts +0 -63
  352. package/src/app/api/marketplace/browse/route.ts +0 -15
  353. package/src/app/api/marketplace/import/route.ts +0 -28
  354. package/src/app/api/marketplace/publish/route.ts +0 -40
  355. package/src/app/api/onboarding/email/route.ts +0 -53
  356. package/src/app/api/settings/telemetry/route.ts +0 -14
  357. package/src/app/api/sync/export/route.ts +0 -54
  358. package/src/app/api/sync/restore/route.ts +0 -37
  359. package/src/app/api/sync/sessions/route.ts +0 -24
  360. package/src/app/auth/callback/route.ts +0 -73
  361. package/src/app/marketplace/page.tsx +0 -19
  362. package/src/components/analytics/analytics-gate-card.tsx +0 -101
  363. package/src/components/marketplace/blueprint-card.tsx +0 -61
  364. package/src/components/marketplace/marketplace-browser.tsx +0 -131
  365. package/src/components/onboarding/email-capture-card.tsx +0 -104
  366. package/src/components/settings/activation-form.tsx +0 -95
  367. package/src/components/settings/cloud-account-section.tsx +0 -147
  368. package/src/components/settings/cloud-sync-section.tsx +0 -155
  369. package/src/components/settings/subscription-section.tsx +0 -410
  370. package/src/components/settings/telemetry-section.tsx +0 -80
  371. package/src/components/shared/premium-gate-overlay.tsx +0 -50
  372. package/src/components/shared/schedule-gate-dialog.tsx +0 -64
  373. package/src/components/shared/upgrade-banner.tsx +0 -112
  374. package/src/hooks/use-supabase-auth.ts +0 -79
  375. package/src/lib/billing/email.ts +0 -54
  376. package/src/lib/billing/products.ts +0 -80
  377. package/src/lib/billing/stripe.ts +0 -101
  378. package/src/lib/cloud/supabase-browser.ts +0 -32
  379. package/src/lib/cloud/supabase-client.ts +0 -56
  380. package/src/lib/license/__tests__/features.test.ts +0 -56
  381. package/src/lib/license/__tests__/key-format.test.ts +0 -88
  382. package/src/lib/license/__tests__/manager.test.ts +0 -64
  383. package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
  384. package/src/lib/license/cloud-validation.ts +0 -60
  385. package/src/lib/license/features.ts +0 -44
  386. package/src/lib/license/key-format.ts +0 -101
  387. package/src/lib/license/limit-check.ts +0 -111
  388. package/src/lib/license/limit-queries.ts +0 -51
  389. package/src/lib/license/manager.ts +0 -345
  390. package/src/lib/license/notifications.ts +0 -59
  391. package/src/lib/license/tier-limits.ts +0 -71
  392. package/src/lib/marketplace/marketplace-client.ts +0 -107
  393. package/src/lib/sync/cloud-sync.ts +0 -235
  394. package/src/lib/telemetry/conversion-events.ts +0 -71
  395. package/src/lib/telemetry/queue.ts +0 -122
  396. package/src/lib/validators/license.ts +0 -33
@@ -2,9 +2,16 @@
2
2
  * Shared helpers and types for Stagent chat MCP tools.
3
3
  */
4
4
 
5
+ import { db } from "@/lib/db";
6
+ import { like } from "drizzle-orm";
7
+ import type { SQLiteTableWithColumns } from "drizzle-orm/sqlite-core";
8
+ import type { SQLiteColumn } from "drizzle-orm/sqlite-core";
9
+
5
10
  /** Context passed to each tool factory — provides project scoping and entity callbacks. */
6
11
  export interface ToolContext {
7
12
  projectId?: string | null;
13
+ /** Absolute path to the active project's working directory. Used by profile tools to surface filesystem skills. */
14
+ projectDir?: string | null;
8
15
  onToolResult?: (toolName: string, result: unknown) => void;
9
16
  }
10
17
 
@@ -22,3 +29,37 @@ export function err(message: string) {
22
29
  isError: true as const,
23
30
  };
24
31
  }
32
+
33
+ /**
34
+ * Resolve an entity ID that may be a prefix (8+ chars) to the full UUID.
35
+ * Uses LIKE 'prefix%' which hits the primary key B-tree index on SQLite.
36
+ *
37
+ * Fast path: IDs >=32 chars are returned as-is (already full UUIDs).
38
+ */
39
+ export async function resolveEntityId(
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ table: SQLiteTableWithColumns<any>,
42
+ idColumn: SQLiteColumn,
43
+ rawId: string,
44
+ ): Promise<{ id: string } | { error: string }> {
45
+ // Full UUIDs are 36 chars (with hyphens) or 32 (without) — skip prefix search
46
+ if (rawId.length >= 32) {
47
+ return { id: rawId };
48
+ }
49
+
50
+ const matches = await db
51
+ .select({ id: idColumn })
52
+ .from(table)
53
+ .where(like(idColumn, `${rawId}%`))
54
+ .limit(2);
55
+
56
+ if (matches.length === 0) {
57
+ return { error: `No entity found matching ID prefix: ${rawId}` };
58
+ }
59
+ if (matches.length > 1) {
60
+ return {
61
+ error: `Ambiguous ID prefix "${rawId}" matches multiple entities: ${matches.map((m) => m.id).join(", ")}. Use the full ID.`,
62
+ };
63
+ }
64
+ return { id: matches[0].id as string };
65
+ }
@@ -3,7 +3,7 @@ import { z } from "zod";
3
3
  import { db } from "@/lib/db";
4
4
  import { notifications } from "@/lib/db/schema";
5
5
  import { eq, isNull, desc } from "drizzle-orm";
6
- import { ok, err, type ToolContext } from "./helpers";
6
+ import { ok, err, resolveEntityId, type ToolContext } from "./helpers";
7
7
 
8
8
  export function notificationTools(_ctx: ToolContext) {
9
9
  return [
@@ -73,13 +73,17 @@ export function notificationTools(_ctx: ToolContext) {
73
73
  },
74
74
  async (args) => {
75
75
  try {
76
+ const resolved = await resolveEntityId(notifications, notifications.id, args.notificationId);
77
+ if ("error" in resolved) return err(resolved.error);
78
+ const notificationId = resolved.id;
79
+
76
80
  const notification = await db
77
81
  .select()
78
82
  .from(notifications)
79
- .where(eq(notifications.id, args.notificationId))
83
+ .where(eq(notifications.id, notificationId))
80
84
  .get();
81
85
 
82
- if (!notification) return err(`Notification not found: ${args.notificationId}`);
86
+ if (!notification) return err(`Notification not found: ${notificationId}`);
83
87
  if (notification.response) return err("Already responded to this notification");
84
88
 
85
89
  const responseData = {
@@ -98,7 +102,7 @@ export function notificationTools(_ctx: ToolContext) {
98
102
  respondedAt: new Date(),
99
103
  read: true,
100
104
  })
101
- .where(eq(notifications.id, args.notificationId));
105
+ .where(eq(notifications.id, notificationId));
102
106
 
103
107
  // Save permanent permission if requested
104
108
  if (args.behavior === "allow" && args.alwaysAllow && notification.toolName && notification.toolInput) {
@@ -115,7 +119,7 @@ export function notificationTools(_ctx: ToolContext) {
115
119
 
116
120
  return ok({
117
121
  message: `Notification ${args.behavior === "allow" ? "approved" : "denied"}`,
118
- notificationId: args.notificationId,
122
+ notificationId,
119
123
  alwaysAllow: args.alwaysAllow ?? false,
120
124
  });
121
125
  } catch (e) {
@@ -2,30 +2,42 @@ import { defineTool } from "../tool-registry";
2
2
  import { z } from "zod";
3
3
  import { ok, err, type ToolContext } from "./helpers";
4
4
 
5
- export function profileTools(_ctx: ToolContext) {
6
- return [
7
- defineTool(
8
- "list_profiles",
9
- "List all available agent profiles with their capabilities and compatible runtimes.",
10
- {},
11
- async () => {
12
- try {
13
- const { listProfiles } = await import("@/lib/agents/profiles/registry");
14
- const profiles = listProfiles();
15
- return ok(
16
- profiles.map((p) => ({
17
- id: p.id,
18
- name: p.name,
19
- description: p.description,
20
- domain: p.domain,
21
- tags: p.tags,
22
- }))
23
- );
24
- } catch (e) {
25
- return err(e instanceof Error ? e.message : "Failed to list profiles");
26
- }
5
+ /**
6
+ * Factory for the list_profiles tool, parameterized by projectDir so it can
7
+ * surface project filesystem skills alongside registry profiles via
8
+ * listFusedProfiles. See features/chat-claude-sdk-skills.md.
9
+ */
10
+ export function getListProfilesTool(projectDir: string | null) {
11
+ return defineTool(
12
+ "list_profiles",
13
+ "List all available agent profiles and filesystem skills with their capabilities and compatible runtimes.",
14
+ {},
15
+ async () => {
16
+ try {
17
+ const { listFusedProfiles } = await import(
18
+ "@/lib/agents/profiles/list-fused-profiles"
19
+ );
20
+ const profiles = await listFusedProfiles(projectDir);
21
+ return ok(
22
+ profiles.map((p) => ({
23
+ id: p.id,
24
+ name: p.name,
25
+ description: p.description,
26
+ domain: p.domain,
27
+ tags: p.tags,
28
+ origin: p.origin ?? "registry",
29
+ }))
30
+ );
31
+ } catch (e) {
32
+ return err(e instanceof Error ? e.message : "Failed to list profiles");
27
33
  }
28
- ),
34
+ }
35
+ );
36
+ }
37
+
38
+ export function profileTools(ctx: ToolContext) {
39
+ return [
40
+ getListProfilesTool(ctx.projectDir ?? null),
29
41
 
30
42
  defineTool(
31
43
  "get_profile",
@@ -44,5 +56,90 @@ export function profileTools(_ctx: ToolContext) {
44
56
  }
45
57
  }
46
58
  ),
59
+
60
+ defineTool(
61
+ "create_profile",
62
+ "Create a new agent profile with a configuration and system prompt (SKILL.md). The profile is saved to ~/.claude/skills/ and becomes immediately available. Use get_profile on an existing profile to see the expected config structure.",
63
+ {
64
+ config: z.object({
65
+ id: z.string().min(1).describe("Unique profile ID (kebab-case, e.g. 'my-analyst')"),
66
+ name: z.string().min(1).describe("Human-readable profile name"),
67
+ version: z.string().regex(/^\d+\.\d+\.\d+$/).describe("Semver version, e.g. '1.0.0'"),
68
+ domain: z.enum(["work", "personal"]).describe("Profile domain"),
69
+ tags: z.array(z.string()).describe("Searchable tags"),
70
+ maxTurns: z.number().positive().optional().describe("Max agent turns per task"),
71
+ outputFormat: z.string().optional().describe("Expected output format hint"),
72
+ author: z.string().optional().describe("Profile author"),
73
+ }).describe("Profile configuration object"),
74
+ skillMd: z.string().min(1).describe(
75
+ "The SKILL.md content — this is the system prompt that defines the agent's behavior, personality, and instructions. Markdown format."
76
+ ),
77
+ },
78
+ async (args) => {
79
+ try {
80
+ const { createProfile } = await import("@/lib/agents/profiles/registry");
81
+ createProfile(args.config, args.skillMd);
82
+ ctx.onToolResult?.("create_profile", { id: args.config.id, name: args.config.name });
83
+ return ok({
84
+ id: args.config.id,
85
+ name: args.config.name,
86
+ message: "Profile created successfully",
87
+ });
88
+ } catch (e) {
89
+ return err(e instanceof Error ? e.message : "Failed to create profile");
90
+ }
91
+ }
92
+ ),
93
+
94
+ defineTool(
95
+ "update_profile",
96
+ "Update an existing agent profile's configuration and/or system prompt. Built-in profiles cannot be modified — duplicate them first with create_profile.",
97
+ {
98
+ profileId: z.string().describe("The profile ID to update"),
99
+ config: z.object({
100
+ id: z.string().min(1),
101
+ name: z.string().min(1),
102
+ version: z.string().regex(/^\d+\.\d+\.\d+$/),
103
+ domain: z.enum(["work", "personal"]),
104
+ tags: z.array(z.string()),
105
+ maxTurns: z.number().positive().optional(),
106
+ outputFormat: z.string().optional(),
107
+ author: z.string().optional(),
108
+ }).describe("Full profile configuration (replaces existing)"),
109
+ skillMd: z.string().min(1).describe("Updated SKILL.md content"),
110
+ },
111
+ async (args) => {
112
+ try {
113
+ const { updateProfile } = await import("@/lib/agents/profiles/registry");
114
+ updateProfile(args.profileId, args.config, args.skillMd);
115
+ ctx.onToolResult?.("update_profile", { id: args.profileId });
116
+ return ok({
117
+ id: args.profileId,
118
+ message: "Profile updated successfully",
119
+ });
120
+ } catch (e) {
121
+ return err(e instanceof Error ? e.message : "Failed to update profile");
122
+ }
123
+ }
124
+ ),
125
+
126
+ defineTool(
127
+ "delete_profile",
128
+ "Delete a custom agent profile. Built-in profiles cannot be deleted.",
129
+ {
130
+ profileId: z.string().describe("The profile ID to delete"),
131
+ },
132
+ async (args) => {
133
+ try {
134
+ const { deleteProfile } = await import("@/lib/agents/profiles/registry");
135
+ deleteProfile(args.profileId);
136
+ return ok({
137
+ message: `Profile "${args.profileId}" deleted`,
138
+ });
139
+ } catch (e) {
140
+ return err(e instanceof Error ? e.message : "Failed to delete profile");
141
+ }
142
+ }
143
+ ),
47
144
  ];
48
145
  }
@@ -85,5 +85,38 @@ export function projectTools(ctx: ToolContext) {
85
85
  }
86
86
  }
87
87
  ),
88
+
89
+ defineTool(
90
+ "delete_project",
91
+ "Permanently delete a project and all its resources (tasks, tables, schedules, " +
92
+ "documents, app instances). This is irreversible. Use to clean up orphaned or " +
93
+ "unwanted projects. Always confirm with the user before calling.",
94
+ {
95
+ projectId: z
96
+ .string()
97
+ .describe("The ID of the project to delete"),
98
+ },
99
+ async (args) => {
100
+ try {
101
+ const { deleteProjectCascade } = await import(
102
+ "@/lib/data/delete-project"
103
+ );
104
+
105
+ const deleted = deleteProjectCascade(args.projectId);
106
+ if (!deleted) {
107
+ return err(`Project "${args.projectId}" not found`);
108
+ }
109
+
110
+ return ok({
111
+ projectId: args.projectId,
112
+ message: `Project "${args.projectId}" and all its resources have been deleted.`,
113
+ });
114
+ } catch (e) {
115
+ return err(
116
+ e instanceof Error ? e.message : "Failed to delete project",
117
+ );
118
+ }
119
+ }
120
+ ),
88
121
  ];
89
122
  }
@@ -3,7 +3,7 @@ import { z } from "zod";
3
3
  import { db } from "@/lib/db";
4
4
  import { schedules } from "@/lib/db/schema";
5
5
  import { eq, and, desc } from "drizzle-orm";
6
- import { ok, err, type ToolContext } from "./helpers";
6
+ import { ok, err, resolveEntityId, type ToolContext } from "./helpers";
7
7
  import { analyzePromptEfficiency } from "@/lib/schedules/prompt-analyzer";
8
8
 
9
9
  const VALID_SCHEDULE_STATUSES = [
@@ -69,6 +69,13 @@ export function scheduleTools(ctx: ToolContext) {
69
69
  .number()
70
70
  .optional()
71
71
  .describe("Auto-expire after this many hours"),
72
+ maxTurns: z
73
+ .number()
74
+ .int()
75
+ .min(10)
76
+ .max(500)
77
+ .optional()
78
+ .describe("Hard cap on turns per firing (10-500). Omit to inherit the system default."),
72
79
  },
73
80
  async (args) => {
74
81
  try {
@@ -147,6 +154,8 @@ export function scheduleTools(ctx: ToolContext) {
147
154
  recurs: true,
148
155
  status: "active",
149
156
  maxFirings: args.maxFirings ?? null,
157
+ maxTurns: args.maxTurns ?? null,
158
+ maxTurnsSetAt: args.maxTurns !== undefined ? now : null,
150
159
  firingCount: 0,
151
160
  expiresAt,
152
161
  nextFireAt,
@@ -184,13 +193,17 @@ export function scheduleTools(ctx: ToolContext) {
184
193
  },
185
194
  async (args) => {
186
195
  try {
196
+ const resolved = await resolveEntityId(schedules, schedules.id, args.scheduleId);
197
+ if ("error" in resolved) return err(resolved.error);
198
+ const scheduleId = resolved.id;
199
+
187
200
  const schedule = await db
188
201
  .select()
189
202
  .from(schedules)
190
- .where(eq(schedules.id, args.scheduleId))
203
+ .where(eq(schedules.id, scheduleId))
191
204
  .get();
192
205
 
193
- if (!schedule) return err(`Schedule not found: ${args.scheduleId}`);
206
+ if (!schedule) return err(`Schedule not found: ${scheduleId}`);
194
207
  ctx.onToolResult?.("get_schedule", schedule);
195
208
  return ok(schedule);
196
209
  } catch (e) {
@@ -216,16 +229,28 @@ export function scheduleTools(ctx: ToolContext) {
216
229
  .describe("New status (use 'paused' to pause, 'active' to resume)"),
217
230
  assignedAgent: z.string().optional().describe("New runtime ID"),
218
231
  agentProfile: z.string().optional().describe("New agent profile"),
232
+ maxTurns: z
233
+ .number()
234
+ .int()
235
+ .min(10)
236
+ .max(500)
237
+ .optional()
238
+ .nullable()
239
+ .describe("Hard cap on turns per firing (10-500). Pass null to clear an override back to the system default."),
219
240
  },
220
241
  async (args) => {
221
242
  try {
243
+ const resolved = await resolveEntityId(schedules, schedules.id, args.scheduleId);
244
+ if ("error" in resolved) return err(resolved.error);
245
+ const scheduleId = resolved.id;
246
+
222
247
  const existing = await db
223
248
  .select()
224
249
  .from(schedules)
225
- .where(eq(schedules.id, args.scheduleId))
250
+ .where(eq(schedules.id, scheduleId))
226
251
  .get();
227
252
 
228
- if (!existing) return err(`Schedule not found: ${args.scheduleId}`);
253
+ if (!existing) return err(`Schedule not found: ${scheduleId}`);
229
254
 
230
255
  const updates: Record<string, unknown> = { updatedAt: new Date() };
231
256
  if (args.name !== undefined) updates.name = args.name;
@@ -233,6 +258,10 @@ export function scheduleTools(ctx: ToolContext) {
233
258
  if (args.status !== undefined) updates.status = args.status;
234
259
  if (args.assignedAgent !== undefined) updates.assignedAgent = args.assignedAgent;
235
260
  if (args.agentProfile !== undefined) updates.agentProfile = args.agentProfile;
261
+ if (args.maxTurns !== undefined) {
262
+ updates.maxTurns = args.maxTurns;
263
+ updates.maxTurnsSetAt = args.maxTurns === null ? null : new Date();
264
+ }
236
265
 
237
266
  if (args.interval) {
238
267
  const { parseInterval, computeNextFireTime } = await import(
@@ -267,12 +296,12 @@ export function scheduleTools(ctx: ToolContext) {
267
296
  await db
268
297
  .update(schedules)
269
298
  .set(updates)
270
- .where(eq(schedules.id, args.scheduleId));
299
+ .where(eq(schedules.id, scheduleId));
271
300
 
272
301
  const [schedule] = await db
273
302
  .select()
274
303
  .from(schedules)
275
- .where(eq(schedules.id, args.scheduleId));
304
+ .where(eq(schedules.id, scheduleId));
276
305
 
277
306
  ctx.onToolResult?.("update_schedule", schedule);
278
307
  return ok(schedule);
@@ -290,16 +319,20 @@ export function scheduleTools(ctx: ToolContext) {
290
319
  },
291
320
  async (args) => {
292
321
  try {
322
+ const resolved = await resolveEntityId(schedules, schedules.id, args.scheduleId);
323
+ if ("error" in resolved) return err(resolved.error);
324
+ const scheduleId = resolved.id;
325
+
293
326
  const existing = await db
294
327
  .select()
295
328
  .from(schedules)
296
- .where(eq(schedules.id, args.scheduleId))
329
+ .where(eq(schedules.id, scheduleId))
297
330
  .get();
298
331
 
299
- if (!existing) return err(`Schedule not found: ${args.scheduleId}`);
332
+ if (!existing) return err(`Schedule not found: ${scheduleId}`);
300
333
 
301
- await db.delete(schedules).where(eq(schedules.id, args.scheduleId));
302
- return ok({ message: "Schedule deleted", scheduleId: args.scheduleId, name: existing.name });
334
+ await db.delete(schedules).where(eq(schedules.id, scheduleId));
335
+ return ok({ message: "Schedule deleted", scheduleId, name: existing.name });
303
336
  } catch (e) {
304
337
  return err(e instanceof Error ? e.message : "Failed to delete schedule");
305
338
  }
@@ -0,0 +1,183 @@
1
+ import { z } from "zod";
2
+ import { defineTool } from "../tool-registry";
3
+ import { ok, err, type ToolContext } from "./helpers";
4
+
5
+ /**
6
+ * Stagent MCP tools for conversation-scoped skill management.
7
+ *
8
+ * Primary consumer: Ollama — the HTTP chat-completion API has no native
9
+ * concept of skills, so Stagent takes over: activate a skill (persist to
10
+ * conversations.active_skill_id) → context builder injects its SKILL.md
11
+ * into Tier 0 of every subsequent turn.
12
+ *
13
+ * Secondary consumer: Claude and Codex runtimes may also call these tools
14
+ * for a programmatic skill-activation path alongside their native Skill
15
+ * handling. The tools themselves are runtime-agnostic — they just bind
16
+ * skill IDs to conversation rows.
17
+ *
18
+ * See `features/chat-ollama-native-skills.md`.
19
+ */
20
+
21
+ // `mergeActiveSkillIds` lives in `@/lib/chat/active-skills` so client code
22
+ // can import the pure helper without pulling this module's `db` import.
23
+ // Re-exported here for back-compat with existing callers (tests, etc.).
24
+ import { mergeActiveSkillIds } from "@/lib/chat/active-skills";
25
+ export { mergeActiveSkillIds };
26
+
27
+ export function skillTools(_ctx: ToolContext) {
28
+ return [
29
+ defineTool(
30
+ "list_skills",
31
+ "List all Stagent-discoverable skills across user (~/.claude, ~/.codex) and project (.claude, .agents) scopes. Returns id, name, tool persona, scope, and a short preview for each. Pass `enriched: true` for additional per-skill metadata (healthScore, syncStatus, linkedProfileId). Read-only.",
32
+ {
33
+ enriched: z
34
+ .boolean()
35
+ .optional()
36
+ .describe(
37
+ "When true, include healthScore ('healthy'|'stale'|'aging'|'unknown'), syncStatus ('synced'|'claude-only'|'codex-only'|'shared'), and linkedProfileId per skill."
38
+ ),
39
+ },
40
+ async (args) => {
41
+ try {
42
+ if (args.enriched) {
43
+ const { listSkillsEnriched } = await import("@/lib/environment/list-skills");
44
+ const skills = listSkillsEnriched();
45
+ return ok({
46
+ count: skills.length,
47
+ skills: skills.map((s) => ({
48
+ id: s.id,
49
+ name: s.name,
50
+ tool: s.tool,
51
+ scope: s.scope,
52
+ preview: s.preview,
53
+ sizeBytes: s.sizeBytes,
54
+ healthScore: s.healthScore,
55
+ syncStatus: s.syncStatus,
56
+ linkedProfileId: s.linkedProfileId,
57
+ })),
58
+ });
59
+ }
60
+ const { listSkills } = await import("@/lib/environment/list-skills");
61
+ const skills = listSkills();
62
+ return ok({
63
+ count: skills.length,
64
+ skills: skills.map((s) => ({
65
+ id: s.id,
66
+ name: s.name,
67
+ tool: s.tool,
68
+ scope: s.scope,
69
+ preview: s.preview,
70
+ sizeBytes: s.sizeBytes,
71
+ })),
72
+ });
73
+ } catch (e) {
74
+ return err(e instanceof Error ? e.message : "list_skills failed");
75
+ }
76
+ }
77
+ ),
78
+
79
+ defineTool(
80
+ "get_skill",
81
+ "Return the full SKILL.md content plus metadata for a single skill, identified by the id returned from list_skills. Use this to preview a skill before activating it.",
82
+ {
83
+ id: z
84
+ .string()
85
+ .describe("Opaque skill ID (from list_skills). Typically the relative path."),
86
+ },
87
+ async (args) => {
88
+ try {
89
+ const { getSkill } = await import("@/lib/environment/list-skills");
90
+ const skill = getSkill(args.id);
91
+ if (!skill) return err(`Skill not found: ${args.id}`);
92
+ return ok({
93
+ id: skill.id,
94
+ name: skill.name,
95
+ tool: skill.tool,
96
+ scope: skill.scope,
97
+ sizeBytes: skill.sizeBytes,
98
+ content: skill.content,
99
+ });
100
+ } catch (e) {
101
+ return err(e instanceof Error ? e.message : "get_skill failed");
102
+ }
103
+ }
104
+ ),
105
+
106
+ defineTool(
107
+ "activate_skill",
108
+ "Activate a skill on a conversation. While active, the skill's SKILL.md is injected into the system prompt on every subsequent turn. Default mode 'replace' clears any prior active skills and binds just this one. Pass mode='add' to compose multiple skills (gated by runtime — Ollama refuses; Claude/Codex/direct allow up to 3). Pass force=true to skip conflict warnings on add.",
109
+ {
110
+ conversationId: z.string().describe("ID of the conversation to bind the skill to."),
111
+ skillId: z.string().describe("Opaque skill ID from list_skills (typically the relative path)."),
112
+ mode: z
113
+ .enum(["replace", "add"])
114
+ .optional()
115
+ .default("replace")
116
+ .describe("'replace' (default) clears prior active skills; 'add' appends — runtime must support composition."),
117
+ force: z
118
+ .boolean()
119
+ .optional()
120
+ .default(false)
121
+ .describe("When mode='add', skip the conflict heuristic check and add anyway."),
122
+ },
123
+ async (args) => {
124
+ const { activateSkill } = await import("@/lib/chat/skill-composition");
125
+ const result = await activateSkill({
126
+ conversationId: args.conversationId,
127
+ skillId: args.skillId,
128
+ mode: args.mode,
129
+ force: args.force,
130
+ });
131
+
132
+ if (result.kind === "error") return err(result.message);
133
+
134
+ if (result.kind === "conflicts") {
135
+ return ok({
136
+ conversationId: args.conversationId,
137
+ requiresConfirmation: true,
138
+ conflicts: result.conflicts,
139
+ hint: "Re-call activate_skill with force=true to add anyway",
140
+ });
141
+ }
142
+
143
+ // kind === "ok"
144
+ if (result.note === "skill already active") {
145
+ return ok({
146
+ conversationId: args.conversationId,
147
+ activeSkillIds: result.activeSkillIds,
148
+ note: result.note,
149
+ });
150
+ }
151
+
152
+ return ok({
153
+ conversationId: args.conversationId,
154
+ activatedSkillId: result.activatedSkillId,
155
+ activeSkillIds: result.activeSkillIds,
156
+ skillName: result.skillName,
157
+ });
158
+ }
159
+ ),
160
+
161
+ defineTool(
162
+ "deactivate_skill",
163
+ "Clear the active skill on a conversation. After this call, subsequent turns will not include any Stagent-injected SKILL.md in the system prompt.",
164
+ {
165
+ conversationId: z
166
+ .string()
167
+ .describe("ID of the conversation to clear the active skill from."),
168
+ },
169
+ async (args) => {
170
+ const { deactivateSkill } = await import("@/lib/chat/skill-composition");
171
+ const result = await deactivateSkill({ conversationId: args.conversationId });
172
+
173
+ if (result.kind === "error") return err(result.message);
174
+
175
+ return ok({
176
+ conversationId: args.conversationId,
177
+ previousSkillId: result.previousSkillId,
178
+ activeSkillId: null,
179
+ });
180
+ }
181
+ ),
182
+ ];
183
+ }