stagent 0.5.0 → 0.6.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 (252) hide show
  1. package/README.md +8 -8
  2. package/dist/cli.js +146 -2
  3. package/docs/.coverage-gaps.json +21 -0
  4. package/docs/.last-generated +1 -1
  5. package/docs/features/agent-intelligence.md +36 -14
  6. package/docs/features/chat.md +33 -56
  7. package/docs/features/cost-usage.md +14 -10
  8. package/docs/features/dashboard-kanban.md +30 -13
  9. package/docs/features/delivery-channels.md +198 -0
  10. package/docs/features/design-system.md +10 -10
  11. package/docs/features/documents.md +8 -8
  12. package/docs/features/home-workspace.md +20 -15
  13. package/docs/features/inbox-notifications.md +22 -10
  14. package/docs/features/keyboard-navigation.md +11 -11
  15. package/docs/features/monitoring.md +1 -1
  16. package/docs/features/playbook.md +30 -32
  17. package/docs/features/profiles.md +33 -11
  18. package/docs/features/projects.md +2 -2
  19. package/docs/features/provider-runtimes.md +58 -14
  20. package/docs/features/schedules.md +70 -40
  21. package/docs/features/settings.md +74 -46
  22. package/docs/features/shared-components.md +7 -15
  23. package/docs/features/tool-permissions.md +9 -9
  24. package/docs/features/workflows.md +32 -21
  25. package/docs/getting-started.md +33 -9
  26. package/docs/index.md +25 -16
  27. package/docs/journeys/developer.md +124 -207
  28. package/docs/journeys/personal-use.md +70 -79
  29. package/docs/journeys/power-user.md +107 -151
  30. package/docs/journeys/work-use.md +81 -113
  31. package/docs/manifest.json +77 -45
  32. package/docs/superpowers/plans/2026-03-30-finish-in-progress-features.md +547 -0
  33. package/docs/use-cases/agency-operator.md +84 -0
  34. package/docs/use-cases/solo-founder.md +75 -0
  35. package/docs/why-stagent.md +59 -0
  36. package/package.json +10 -3
  37. package/src/app/api/channels/[id]/route.ts +103 -0
  38. package/src/app/api/channels/[id]/test/route.ts +52 -0
  39. package/src/app/api/channels/inbound/slack/route.ts +109 -0
  40. package/src/app/api/channels/inbound/telegram/poll/route.ts +128 -0
  41. package/src/app/api/channels/inbound/telegram/route.ts +76 -0
  42. package/src/app/api/channels/route.ts +71 -0
  43. package/src/app/api/chat/conversations/route.ts +15 -0
  44. package/src/app/api/chat/entities/search/route.ts +46 -31
  45. package/src/app/api/environment/profiles/suggest/route.ts +19 -3
  46. package/src/app/api/environment/scan/route.ts +8 -1
  47. package/src/app/api/handoffs/[id]/route.ts +76 -0
  48. package/src/app/api/handoffs/route.ts +89 -0
  49. package/src/app/api/memory/route.ts +181 -0
  50. package/src/app/api/profiles/[id]/route.ts +16 -1
  51. package/src/app/api/profiles/[id]/test/route.ts +4 -0
  52. package/src/app/api/profiles/[id]/test-results/route.ts +22 -0
  53. package/src/app/api/profiles/[id]/test-single/route.ts +64 -0
  54. package/src/app/api/profiles/assist/route.ts +35 -0
  55. package/src/app/api/profiles/import-repo/apply-updates/route.ts +123 -0
  56. package/src/app/api/profiles/import-repo/check-updates/route.ts +163 -0
  57. package/src/app/api/profiles/import-repo/confirm/route.ts +118 -0
  58. package/src/app/api/profiles/import-repo/preview/route.ts +107 -0
  59. package/src/app/api/profiles/import-repo/route.ts +29 -0
  60. package/src/app/api/profiles/import-repo/scan/route.ts +25 -0
  61. package/src/app/api/profiles/route.ts +73 -22
  62. package/src/app/api/runtimes/ollama/route.ts +86 -0
  63. package/src/app/api/runtimes/suggest/route.ts +29 -0
  64. package/src/app/api/schedules/[id]/heartbeat-history/route.ts +77 -0
  65. package/src/app/api/schedules/[id]/route.ts +41 -3
  66. package/src/app/api/schedules/parse/route.ts +66 -0
  67. package/src/app/api/schedules/route.ts +71 -12
  68. package/src/app/api/settings/author-default/route.ts +7 -0
  69. package/src/app/api/settings/learning/route.ts +41 -0
  70. package/src/app/api/settings/ollama/route.ts +34 -0
  71. package/src/app/api/settings/providers/route.ts +57 -0
  72. package/src/app/api/settings/routing/route.ts +24 -0
  73. package/src/app/api/settings/web-search/route.ts +28 -0
  74. package/src/app/api/tasks/[id]/execute/route.ts +13 -1
  75. package/src/app/documents/page.tsx +3 -0
  76. package/src/app/environment/page.tsx +8 -1
  77. package/src/app/settings/page.tsx +10 -4
  78. package/src/app/workflows/[id]/edit/page.tsx +2 -0
  79. package/src/app/workflows/new/page.tsx +2 -0
  80. package/src/components/chat/chat-command-popover.tsx +22 -19
  81. package/src/components/chat/chat-input.tsx +5 -0
  82. package/src/components/chat/chat-model-selector.tsx +42 -1
  83. package/src/components/chat/chat-shell.tsx +2 -0
  84. package/src/components/dashboard/welcome-landing.tsx +9 -9
  85. package/src/components/environment/artifact-card.tsx +27 -1
  86. package/src/components/environment/environment-dashboard.tsx +50 -2
  87. package/src/components/environment/environment-summary-card.tsx +5 -2
  88. package/src/components/environment/suggested-profiles.tsx +117 -52
  89. package/src/components/handoffs/handoff-approval-card.tsx +159 -0
  90. package/src/components/memory/memory-browser.tsx +315 -0
  91. package/src/components/profiles/learned-context-panel.tsx +4 -4
  92. package/src/components/profiles/profile-assist-panel.tsx +512 -0
  93. package/src/components/profiles/profile-browser.tsx +109 -8
  94. package/src/components/profiles/profile-card.tsx +29 -1
  95. package/src/components/profiles/profile-detail-view.tsx +200 -28
  96. package/src/components/profiles/profile-form-view.tsx +220 -82
  97. package/src/components/profiles/repo-import-wizard.tsx +648 -0
  98. package/src/components/profiles/smoke-test-editor.tsx +106 -0
  99. package/src/components/schedules/schedule-create-sheet.tsx +9 -1
  100. package/src/components/schedules/schedule-form.tsx +348 -9
  101. package/src/components/schedules/schedule-list.tsx +15 -2
  102. package/src/components/settings/auth-method-selector.tsx +7 -1
  103. package/src/components/settings/budget-guardrails-section.tsx +111 -48
  104. package/src/components/settings/channels-section.tsx +526 -0
  105. package/src/components/settings/chat-settings-section.tsx +27 -1
  106. package/src/components/settings/data-management-section.tsx +8 -6
  107. package/src/components/settings/learning-context-section.tsx +124 -0
  108. package/src/components/settings/ollama-section.tsx +270 -0
  109. package/src/components/settings/providers-runtimes-section.tsx +499 -0
  110. package/src/components/settings/web-search-section.tsx +101 -0
  111. package/src/components/shared/tag-input.tsx +156 -0
  112. package/src/components/tasks/kanban-board.tsx +32 -0
  113. package/src/components/tasks/kanban-column.tsx +4 -2
  114. package/src/components/tasks/task-card.tsx +1 -0
  115. package/src/components/tasks/task-chip-bar.tsx +6 -1
  116. package/src/components/tasks/task-create-panel.tsx +55 -5
  117. package/src/components/workflows/workflow-form-view.tsx +38 -3
  118. package/src/hooks/use-chat-autocomplete.ts +24 -26
  119. package/src/hooks/use-project-skills.ts +66 -0
  120. package/src/hooks/use-tag-suggestions.ts +31 -0
  121. package/src/instrumentation.ts +4 -1
  122. package/src/lib/agents/__tests__/claude-agent.test.ts +3 -0
  123. package/src/lib/agents/__tests__/learned-context.test.ts +10 -0
  124. package/src/lib/agents/agentic-loop.ts +235 -0
  125. package/src/lib/agents/browser-mcp.ts +59 -4
  126. package/src/lib/agents/claude-agent.ts +26 -199
  127. package/src/lib/agents/handoff/bus.ts +164 -0
  128. package/src/lib/agents/handoff/governance.ts +47 -0
  129. package/src/lib/agents/handoff/types.ts +16 -0
  130. package/src/lib/agents/learned-context.ts +27 -7
  131. package/src/lib/agents/memory/decay.ts +61 -0
  132. package/src/lib/agents/memory/extractor.ts +181 -0
  133. package/src/lib/agents/memory/retrieval.ts +96 -0
  134. package/src/lib/agents/memory/types.ts +6 -0
  135. package/src/lib/agents/profiles/__tests__/project-profiles.test.ts +119 -0
  136. package/src/lib/agents/profiles/__tests__/registry.test.ts +11 -3
  137. package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +2 -2
  138. package/src/lib/agents/profiles/builtins/content-creator/SKILL.md +19 -0
  139. package/src/lib/agents/profiles/builtins/content-creator/profile.yaml +27 -0
  140. package/src/lib/agents/profiles/builtins/customer-support-agent/SKILL.md +19 -0
  141. package/src/lib/agents/profiles/builtins/customer-support-agent/profile.yaml +26 -0
  142. package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +2 -2
  143. package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +2 -2
  144. package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +2 -2
  145. package/src/lib/agents/profiles/builtins/financial-analyst/SKILL.md +19 -0
  146. package/src/lib/agents/profiles/builtins/financial-analyst/profile.yaml +24 -0
  147. package/src/lib/agents/profiles/builtins/general/profile.yaml +2 -2
  148. package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +2 -2
  149. package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +2 -2
  150. package/src/lib/agents/profiles/builtins/marketing-strategist/SKILL.md +19 -0
  151. package/src/lib/agents/profiles/builtins/marketing-strategist/profile.yaml +27 -0
  152. package/src/lib/agents/profiles/builtins/operations-coordinator/SKILL.md +19 -0
  153. package/src/lib/agents/profiles/builtins/operations-coordinator/profile.yaml +26 -0
  154. package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +2 -2
  155. package/src/lib/agents/profiles/builtins/researcher/SKILL.md +1 -0
  156. package/src/lib/agents/profiles/builtins/researcher/profile.yaml +2 -2
  157. package/src/lib/agents/profiles/builtins/sales-researcher/SKILL.md +19 -0
  158. package/src/lib/agents/profiles/builtins/sales-researcher/profile.yaml +26 -0
  159. package/src/lib/agents/profiles/builtins/shopping-assistant/SKILL.md +1 -0
  160. package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +2 -2
  161. package/src/lib/agents/profiles/builtins/sweep/profile.yaml +1 -1
  162. package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +2 -2
  163. package/src/lib/agents/profiles/builtins/travel-planner/SKILL.md +2 -0
  164. package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +2 -2
  165. package/src/lib/agents/profiles/builtins/wealth-manager/SKILL.md +2 -0
  166. package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +2 -2
  167. package/src/lib/agents/profiles/project-profiles.ts +193 -0
  168. package/src/lib/agents/profiles/registry.ts +130 -6
  169. package/src/lib/agents/profiles/types.ts +28 -0
  170. package/src/lib/agents/router.ts +174 -2
  171. package/src/lib/agents/runtime/__tests__/catalog.test.ts +15 -4
  172. package/src/lib/agents/runtime/anthropic-direct.ts +644 -0
  173. package/src/lib/agents/runtime/catalog.ts +57 -2
  174. package/src/lib/agents/runtime/claude.ts +205 -1
  175. package/src/lib/agents/runtime/index.ts +22 -0
  176. package/src/lib/agents/runtime/ollama-adapter.ts +409 -0
  177. package/src/lib/agents/runtime/openai-direct.ts +514 -0
  178. package/src/lib/agents/runtime/profile-assist-types.ts +30 -0
  179. package/src/lib/agents/runtime/types.ts +2 -0
  180. package/src/lib/agents/tool-permissions.ts +203 -0
  181. package/src/lib/channels/gateway.ts +321 -0
  182. package/src/lib/channels/poller.ts +268 -0
  183. package/src/lib/channels/registry.ts +90 -0
  184. package/src/lib/channels/slack-adapter.ts +188 -0
  185. package/src/lib/channels/telegram-adapter.ts +218 -0
  186. package/src/lib/channels/types.ts +43 -0
  187. package/src/lib/channels/webhook-adapter.ts +74 -0
  188. package/src/lib/chat/context-builder.ts +22 -2
  189. package/src/lib/chat/engine.ts +95 -13
  190. package/src/lib/chat/ollama-engine.ts +198 -0
  191. package/src/lib/chat/stagent-tools.ts +106 -20
  192. package/src/lib/chat/tool-catalog.ts +24 -0
  193. package/src/lib/chat/tool-registry.ts +90 -0
  194. package/src/lib/chat/tools/chat-history-tools.ts +4 -4
  195. package/src/lib/chat/tools/document-tools.ts +7 -7
  196. package/src/lib/chat/tools/handoff-tools.ts +70 -0
  197. package/src/lib/chat/tools/notification-tools.ts +4 -4
  198. package/src/lib/chat/tools/profile-tools.ts +3 -3
  199. package/src/lib/chat/tools/project-tools.ts +3 -3
  200. package/src/lib/chat/tools/schedule-tools.ts +29 -13
  201. package/src/lib/chat/tools/settings-tools.ts +2 -2
  202. package/src/lib/chat/tools/task-tools.ts +66 -11
  203. package/src/lib/chat/tools/usage-tools.ts +2 -2
  204. package/src/lib/chat/tools/workflow-tools.ts +8 -8
  205. package/src/lib/chat/types.ts +11 -5
  206. package/src/lib/constants/known-tools.ts +19 -0
  207. package/src/lib/constants/prose-styles.ts +1 -1
  208. package/src/lib/constants/settings.ts +7 -0
  209. package/src/lib/data/channel-bindings.ts +85 -0
  210. package/src/lib/data/clear.ts +22 -0
  211. package/src/lib/data/profile-test-results.ts +48 -0
  212. package/src/lib/data/seed-data/conversations.ts +196 -0
  213. package/src/lib/data/seed-data/learned-context.ts +99 -0
  214. package/src/lib/data/seed-data/notifications.ts +54 -1
  215. package/src/lib/data/seed-data/profile-test-results.ts +96 -0
  216. package/src/lib/data/seed-data/repo-imports.ts +51 -0
  217. package/src/lib/data/seed-data/views.ts +60 -0
  218. package/src/lib/data/seed.ts +51 -0
  219. package/src/lib/db/bootstrap.ts +162 -0
  220. package/src/lib/db/migrations/0013_add_repo_imports.sql +15 -0
  221. package/src/lib/db/migrations/0014_add_linked_profile_id.sql +3 -0
  222. package/src/lib/db/migrations/0015_add_channel_bindings.sql +23 -0
  223. package/src/lib/db/schema.ts +187 -1
  224. package/src/lib/environment/__tests__/auto-scan.test.ts +86 -0
  225. package/src/lib/environment/__tests__/profile-linker.test.ts +187 -0
  226. package/src/lib/environment/auto-scan.ts +48 -0
  227. package/src/lib/environment/data.ts +25 -0
  228. package/src/lib/environment/profile-generator.ts +40 -10
  229. package/src/lib/environment/profile-linker.ts +143 -0
  230. package/src/lib/environment/profile-rules.ts +96 -0
  231. package/src/lib/import/dedup.ts +149 -0
  232. package/src/lib/import/format-adapter.ts +631 -0
  233. package/src/lib/import/github-api.ts +219 -0
  234. package/src/lib/import/repo-scanner.ts +251 -0
  235. package/src/lib/schedules/__tests__/nlp-parser.test.ts +330 -0
  236. package/src/lib/schedules/active-hours.ts +120 -0
  237. package/src/lib/schedules/heartbeat-parser.ts +224 -0
  238. package/src/lib/schedules/heartbeat-prompt.ts +153 -0
  239. package/src/lib/schedules/nlp-parser.ts +357 -0
  240. package/src/lib/schedules/scheduler.ts +218 -3
  241. package/src/lib/settings/__tests__/budget-guardrails.test.ts +39 -1
  242. package/src/lib/settings/helpers.ts +6 -0
  243. package/src/lib/settings/routing.ts +24 -0
  244. package/src/lib/settings/runtime-setup.ts +28 -1
  245. package/src/lib/usage/ledger.ts +2 -1
  246. package/src/lib/validators/__tests__/settings.test.ts +9 -0
  247. package/src/lib/validators/profile.ts +39 -0
  248. package/src/lib/workflows/blueprints/builtins/business-daily-briefing.yaml +102 -0
  249. package/src/lib/workflows/blueprints/builtins/content-marketing-pipeline.yaml +90 -0
  250. package/src/lib/workflows/blueprints/builtins/customer-support-triage.yaml +107 -0
  251. package/src/lib/workflows/blueprints/builtins/financial-reporting.yaml +104 -0
  252. package/src/lib/workflows/blueprints/builtins/lead-research-pipeline.yaml +82 -0
@@ -0,0 +1,57 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getRuntimeSetupStates } from "@/lib/settings/runtime-setup";
3
+ import { getRoutingPreference } from "@/lib/settings/routing";
4
+ import { getAuthSettings } from "@/lib/settings/auth";
5
+ import { getOpenAIAuthSettings } from "@/lib/settings/openai-auth";
6
+
7
+ export async function GET() {
8
+ const [runtimeStates, routingPreference, anthropicAuth, openaiAuth] =
9
+ await Promise.all([
10
+ getRuntimeSetupStates(),
11
+ getRoutingPreference(),
12
+ getAuthSettings(),
13
+ getOpenAIAuthSettings(),
14
+ ]);
15
+
16
+ const anthropicConfigured =
17
+ runtimeStates["claude-code"].configured ||
18
+ runtimeStates["anthropic-direct"].configured;
19
+ const openaiConfigured =
20
+ runtimeStates["openai-codex-app-server"].configured ||
21
+ runtimeStates["openai-direct"].configured;
22
+
23
+ // Detect dual-billing: user has OAuth (subscription) for Claude Code
24
+ // AND an API key (pay-as-you-go) for Anthropic Direct
25
+ const anthropicHasOAuth =
26
+ anthropicAuth.method === "oauth" || anthropicAuth.apiKeySource === "oauth";
27
+ const anthropicHasApiKey = anthropicAuth.hasKey;
28
+ const anthropicDualBilling = anthropicHasOAuth && anthropicHasApiKey;
29
+
30
+ return NextResponse.json({
31
+ providers: {
32
+ anthropic: {
33
+ configured: anthropicConfigured,
34
+ authMethod: anthropicAuth.method,
35
+ hasKey: anthropicAuth.hasKey,
36
+ apiKeySource: anthropicAuth.apiKeySource,
37
+ dualBilling: anthropicDualBilling,
38
+ runtimes: [
39
+ runtimeStates["claude-code"],
40
+ runtimeStates["anthropic-direct"],
41
+ ],
42
+ },
43
+ openai: {
44
+ configured: openaiConfigured,
45
+ hasKey: openaiAuth.hasKey,
46
+ apiKeySource: openaiAuth.apiKeySource,
47
+ dualBilling: false,
48
+ runtimes: [
49
+ runtimeStates["openai-codex-app-server"],
50
+ runtimeStates["openai-direct"],
51
+ ],
52
+ },
53
+ },
54
+ routingPreference,
55
+ configuredProviderCount: Number(anthropicConfigured) + Number(openaiConfigured),
56
+ });
57
+ }
@@ -0,0 +1,24 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getRoutingPreference, setRoutingPreference } from "@/lib/settings/routing";
3
+ import type { RoutingPreference } from "@/lib/constants/settings";
4
+
5
+ const VALID_VALUES: RoutingPreference[] = ["cost", "latency", "quality", "manual"];
6
+
7
+ export async function GET() {
8
+ const preference = await getRoutingPreference();
9
+ return NextResponse.json({ preference });
10
+ }
11
+
12
+ export async function POST(req: NextRequest) {
13
+ const body = await req.json();
14
+
15
+ if (!body.preference || !VALID_VALUES.includes(body.preference)) {
16
+ return NextResponse.json(
17
+ { error: `preference must be one of: ${VALID_VALUES.join(", ")}` },
18
+ { status: 400 },
19
+ );
20
+ }
21
+
22
+ await setRoutingPreference(body.preference);
23
+ return NextResponse.json({ preference: body.preference });
24
+ }
@@ -0,0 +1,28 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getSetting, setSetting } from "@/lib/settings/helpers";
3
+ import { SETTINGS_KEYS } from "@/lib/constants/settings";
4
+
5
+ export async function GET() {
6
+ const exaEnabled = await getSetting(SETTINGS_KEYS.EXA_SEARCH_MCP_ENABLED);
7
+
8
+ return NextResponse.json({
9
+ exaSearchEnabled: exaEnabled === "true",
10
+ });
11
+ }
12
+
13
+ export async function POST(req: NextRequest) {
14
+ const body = await req.json();
15
+
16
+ if (body.exaSearchEnabled !== undefined) {
17
+ await setSetting(
18
+ SETTINGS_KEYS.EXA_SEARCH_MCP_ENABLED,
19
+ body.exaSearchEnabled ? "true" : "false"
20
+ );
21
+ }
22
+
23
+ const exaEnabled = await getSetting(SETTINGS_KEYS.EXA_SEARCH_MCP_ENABLED);
24
+
25
+ return NextResponse.json({
26
+ exaSearchEnabled: exaEnabled === "true",
27
+ });
28
+ }
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  import { db } from "@/lib/db";
3
- import { tasks } from "@/lib/db/schema";
3
+ import { tasks, projects } from "@/lib/db/schema";
4
4
  import { eq, and } from "drizzle-orm";
5
5
  import { executeTaskWithAgent, classifyTaskProfile } from "@/lib/agents/router";
6
6
  import { DEFAULT_AGENT_RUNTIME } from "@/lib/agents/runtime/catalog";
@@ -9,6 +9,7 @@ import {
9
9
  BudgetLimitExceededError,
10
10
  enforceTaskBudgetGuardrails,
11
11
  } from "@/lib/settings/budget-guardrails";
12
+ import { ensureFreshScan } from "@/lib/environment/auto-scan";
12
13
 
13
14
  export async function POST(
14
15
  _req: NextRequest,
@@ -47,6 +48,17 @@ export async function POST(
47
48
 
48
49
  const task = claimed[0];
49
50
 
51
+ // Auto-scan environment if the task's project has a workingDirectory
52
+ if (task.projectId) {
53
+ const [project] = await db
54
+ .select()
55
+ .from(projects)
56
+ .where(eq(projects.id, task.projectId));
57
+ if (project?.workingDirectory) {
58
+ ensureFreshScan(project.workingDirectory, task.projectId);
59
+ }
60
+ }
61
+
50
62
  // Auto-classify profile if none was set
51
63
  if (!task.agentProfile) {
52
64
  const autoProfile = classifyTaskProfile(
@@ -24,6 +24,9 @@ export default async function DocumentsPage() {
24
24
  extractedText: documents.extractedText,
25
25
  processedPath: documents.processedPath,
26
26
  processingError: documents.processingError,
27
+ source: documents.source,
28
+ conversationId: documents.conversationId,
29
+ messageId: documents.messageId,
27
30
  createdAt: documents.createdAt,
28
31
  updatedAt: documents.updatedAt,
29
32
  taskTitle: tasks.title,
@@ -3,11 +3,16 @@ import { listTemplates } from "@/lib/environment/templates";
3
3
  import { calculateHealthScore } from "@/lib/environment/health-scoring";
4
4
  import { EnvironmentDashboard } from "@/components/environment/environment-dashboard";
5
5
  import { PageShell } from "@/components/shared/page-shell";
6
- import { getWorkspaceContext } from "@/lib/environment/workspace-context";
6
+ import { getWorkspaceContext, getLaunchCwd } from "@/lib/environment/workspace-context";
7
+ import { ensureFreshScan } from "@/lib/environment/auto-scan";
7
8
 
8
9
  export const dynamic = "force-dynamic";
9
10
 
10
11
  export default async function EnvironmentPage() {
12
+ // Auto-scan the workspace directory if stale or missing
13
+ const cwd = getLaunchCwd();
14
+ ensureFreshScan(cwd);
15
+
11
16
  const scan = getLatestScan();
12
17
 
13
18
  if (!scan) {
@@ -18,6 +23,7 @@ export default async function EnvironmentPage() {
18
23
  artifacts={[]}
19
24
  categoryCounts={[]}
20
25
  toolCounts={[]}
26
+ scanPath={cwd}
21
27
  />
22
28
  </PageShell>
23
29
  );
@@ -48,6 +54,7 @@ export default async function EnvironmentPage() {
48
54
  checkpoints={checkpoints}
49
55
  templates={templates}
50
56
  healthScore={healthScore}
57
+ scanPath={cwd}
51
58
  />
52
59
  </PageShell>
53
60
  );
@@ -1,11 +1,14 @@
1
- import { AuthConfigSection } from "@/components/settings/auth-config-section";
2
- import { OpenAIRuntimeSection } from "@/components/settings/openai-runtime-section";
1
+ import { ProvidersAndRuntimesSection } from "@/components/settings/providers-runtimes-section";
3
2
  import { PermissionsSections } from "@/components/settings/permissions-sections";
4
3
  import { DataManagementSection } from "@/components/settings/data-management-section";
5
4
  import { BudgetGuardrailsSection } from "@/components/settings/budget-guardrails-section";
6
5
  import { ChatSettingsSection } from "@/components/settings/chat-settings-section";
7
6
  import { RuntimeTimeoutSection } from "@/components/settings/runtime-timeout-section";
8
7
  import { BrowserToolsSection } from "@/components/settings/browser-tools-section";
8
+ import { WebSearchSection } from "@/components/settings/web-search-section";
9
+ import { LearningContextSection } from "@/components/settings/learning-context-section";
10
+ import { OllamaSection } from "@/components/settings/ollama-section";
11
+ import { ChannelsSection } from "@/components/settings/channels-section";
9
12
  import { PageShell } from "@/components/shared/page-shell";
10
13
 
11
14
  export const dynamic = "force-dynamic";
@@ -14,11 +17,14 @@ export default function SettingsPage() {
14
17
  return (
15
18
  <PageShell title="Settings" description="Manage your Stagent configuration">
16
19
  <div className="space-y-6">
17
- <AuthConfigSection />
18
- <OpenAIRuntimeSection />
20
+ <ProvidersAndRuntimesSection />
21
+ <OllamaSection />
19
22
  <ChatSettingsSection />
20
23
  <RuntimeTimeoutSection />
24
+ <LearningContextSection />
25
+ <WebSearchSection />
21
26
  <BrowserToolsSection />
27
+ <ChannelsSection />
22
28
  <BudgetGuardrailsSection />
23
29
  <PermissionsSections />
24
30
  <DataManagementSection />
@@ -34,6 +34,8 @@ export default async function EditWorkflowPage({
34
34
  id: p.id,
35
35
  name: p.name,
36
36
  supportedRuntimes: p.supportedRuntimes,
37
+ origin: p.origin,
38
+ scope: p.scope,
37
39
  }));
38
40
 
39
41
  const workflowData = {
@@ -16,6 +16,8 @@ export default async function NewWorkflowPage() {
16
16
  id: p.id,
17
17
  name: p.name,
18
18
  supportedRuntimes: p.supportedRuntimes,
19
+ origin: p.origin,
20
+ scope: p.scope,
19
21
  }));
20
22
 
21
23
  return (
@@ -21,7 +21,7 @@ import {
21
21
  } from "lucide-react";
22
22
  import type { LucideIcon } from "lucide-react";
23
23
  import {
24
- getToolCatalog,
24
+ getToolCatalogWithSkills,
25
25
  groupToolCatalog,
26
26
  TOOL_GROUP_ICONS,
27
27
  TOOL_GROUP_ORDER,
@@ -36,6 +36,7 @@ interface ChatCommandPopoverProps {
36
36
  anchorRect: { top: number; left: number; height: number } | null;
37
37
  entityResults: EntitySearchResult[];
38
38
  entityLoading: boolean;
39
+ projectProfiles?: Array<{ id: string; name: string; description: string }>;
39
40
  onSelect: (item: {
40
41
  type: "slash" | "mention";
41
42
  id: string;
@@ -81,6 +82,7 @@ export function ChatCommandPopover({
81
82
  anchorRect,
82
83
  entityResults,
83
84
  entityLoading,
85
+ projectProfiles,
84
86
  onSelect,
85
87
  onClose,
86
88
  }: ChatCommandPopoverProps) {
@@ -116,7 +118,7 @@ export function ChatCommandPopover({
116
118
  data-chat-autocomplete=""
117
119
  className="rounded-lg border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95 slide-in-from-bottom-2"
118
120
  >
119
- <Command shouldFilter={mode === "slash"} loop>
121
+ <Command shouldFilter loop>
120
122
  {/* Hidden input for cmdk filtering — synced to query */}
121
123
  <div className="sr-only">
122
124
  <CommandInput value={query} />
@@ -127,13 +129,17 @@ export function ChatCommandPopover({
127
129
  {mode === "slash" ? "No matching tools" : "No matching entities"}
128
130
  </CommandEmpty>
129
131
 
130
- {mode === "slash" && <ToolCatalogItems onSelect={onSelect} />}
132
+ {mode === "slash" && (
133
+ <ToolCatalogItems
134
+ onSelect={onSelect}
135
+ projectProfiles={projectProfiles}
136
+ />
137
+ )}
131
138
 
132
139
  {mode === "mention" && (
133
140
  <MentionItems
134
141
  results={entityResults}
135
142
  loading={entityLoading}
136
- query={query}
137
143
  onSelect={onSelect}
138
144
  />
139
145
  )}
@@ -147,10 +153,15 @@ export function ChatCommandPopover({
147
153
 
148
154
  function ToolCatalogItems({
149
155
  onSelect,
156
+ projectProfiles,
150
157
  }: {
151
158
  onSelect: ChatCommandPopoverProps["onSelect"];
159
+ projectProfiles?: ChatCommandPopoverProps["projectProfiles"];
152
160
  }) {
153
- const catalog = getToolCatalog({ includeBrowser: true });
161
+ const catalog = getToolCatalogWithSkills({
162
+ includeBrowser: true,
163
+ projectProfiles,
164
+ });
154
165
  const groups = groupToolCatalog(catalog);
155
166
 
156
167
  return (
@@ -172,7 +183,9 @@ function ToolCatalogItems({
172
183
  label: entry.name,
173
184
  text: entry.behavior === "execute_immediately"
174
185
  ? entry.name
175
- : `Use ${entry.name} to `,
186
+ : entry.group === "Skills"
187
+ ? `Use the ${entry.name} profile: `
188
+ : `Use ${entry.name} to `,
176
189
  })
177
190
  }
178
191
  >
@@ -200,27 +213,17 @@ function ToolCatalogItems({
200
213
  function MentionItems({
201
214
  results,
202
215
  loading,
203
- query,
204
216
  onSelect,
205
217
  }: {
206
218
  results: EntitySearchResult[];
207
219
  loading: boolean;
208
- query: string;
209
220
  onSelect: ChatCommandPopoverProps["onSelect"];
210
221
  }) {
211
222
  if (loading && results.length === 0) {
212
223
  return (
213
224
  <div className="flex items-center gap-2 px-3 py-4 text-sm text-muted-foreground">
214
225
  <Loader2 className="h-4 w-4 animate-spin" />
215
- Searching...
216
- </div>
217
- );
218
- }
219
-
220
- if (!query) {
221
- return (
222
- <div className="px-3 py-4 text-sm text-muted-foreground text-center">
223
- Type to search entities...
226
+ Loading...
224
227
  </div>
225
228
  );
226
229
  }
@@ -228,7 +231,7 @@ function MentionItems({
228
231
  const grouped = groupByType(results);
229
232
  const entityTypes = Object.keys(grouped);
230
233
 
231
- if (entityTypes.length === 0 && !loading) {
234
+ if (entityTypes.length === 0) {
232
235
  return null; // CommandEmpty will show
233
236
  }
234
237
 
@@ -242,7 +245,7 @@ function MentionItems({
242
245
  {grouped[type].map((entity) => (
243
246
  <CommandItem
244
247
  key={`${entity.entityType}-${entity.entityId}`}
245
- value={`${entity.entityType} ${entity.label}`}
248
+ value={`${entity.entityType} ${entity.label} ${entity.description ?? ""} ${entity.status ?? ""}`}
246
249
  onSelect={() =>
247
250
  onSelect({
248
251
  type: "mention",
@@ -8,6 +8,7 @@ import { ChatModelSelector } from "./chat-model-selector";
8
8
  import { ChatCommandPopover } from "./chat-command-popover";
9
9
  import { useChatAutocomplete, type MentionReference } from "@/hooks/use-chat-autocomplete";
10
10
  import { getToolCatalog } from "@/lib/chat/tool-catalog";
11
+ import { useProjectSkills } from "@/hooks/use-project-skills";
11
12
  import type { ChatModelOption } from "@/lib/chat/types";
12
13
 
13
14
  interface ChatInputProps {
@@ -19,6 +20,7 @@ interface ChatInputProps {
19
20
  modelId?: string;
20
21
  onModelChange?: (modelId: string) => void;
21
22
  availableModels?: ChatModelOption[];
23
+ projectId?: string | null;
22
24
  }
23
25
 
24
26
  export function ChatInput({
@@ -30,10 +32,12 @@ export function ChatInput({
30
32
  modelId,
31
33
  onModelChange,
32
34
  availableModels,
35
+ projectId,
33
36
  }: ChatInputProps) {
34
37
  const [value, setValue] = useState("");
35
38
  const textareaRef = useRef<HTMLTextAreaElement>(null);
36
39
  const autocomplete = useChatAutocomplete();
40
+ const { skills: projectSkills } = useProjectSkills(projectId);
37
41
 
38
42
  // Sync textarea ref with autocomplete hook
39
43
  useEffect(() => {
@@ -206,6 +210,7 @@ export function ChatInput({
206
210
  anchorRect={autocomplete.state.anchorRect}
207
211
  entityResults={autocomplete.entityResults}
208
212
  entityLoading={autocomplete.entityLoading}
213
+ projectProfiles={projectSkills.length > 0 ? projectSkills : undefined}
209
214
  onSelect={handlePopoverSelect}
210
215
  onClose={autocomplete.close}
211
216
  />
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useEffect, useState } from "react";
3
4
  import { CHAT_MODELS, DEFAULT_CHAT_MODEL, type ChatModelOption } from "@/lib/chat/types";
4
5
  import { Button } from "@/components/ui/button";
5
6
  import {
@@ -24,6 +25,7 @@ const tierEmoji: Record<string, string> = {
24
25
  Fast: "\u26A1",
25
26
  Balanced: "\u2728",
26
27
  Best: "\uD83D\uDC8E",
28
+ Local: "\uD83C\uDFE0",
27
29
  };
28
30
 
29
31
  export function ChatModelSelector({
@@ -31,7 +33,27 @@ export function ChatModelSelector({
31
33
  onModelChange,
32
34
  models = CHAT_MODELS,
33
35
  }: ChatModelSelectorProps) {
34
- const current = models.find((m) => m.id === modelId) ?? models[0] ?? { id: modelId, label: modelId, provider: "anthropic" as const, tier: "Balanced", costLabel: "$$" };
36
+ const [ollamaModels, setOllamaModels] = useState<ChatModelOption[]>([]);
37
+
38
+ // Fetch available Ollama models on mount
39
+ useEffect(() => {
40
+ fetch("/api/runtimes/ollama")
41
+ .then((r) => (r.ok ? r.json() : { models: [] }))
42
+ .then((data: { models?: Array<{ name: string; details?: { parameter_size?: string } }> }) => {
43
+ const models = (data.models ?? []).map((m) => ({
44
+ id: `ollama:${m.name}`,
45
+ label: m.name.replace(/:latest$/, ""),
46
+ provider: "ollama" as const,
47
+ tier: "Local",
48
+ costLabel: "Free",
49
+ }));
50
+ setOllamaModels(models);
51
+ })
52
+ .catch(() => {});
53
+ }, []);
54
+
55
+ const allModels = [...models, ...ollamaModels];
56
+ const current = allModels.find((m) => m.id === modelId) ?? allModels[0] ?? { id: modelId, label: modelId, provider: "anthropic" as const, tier: "Balanced", costLabel: "$$" };
35
57
 
36
58
  const anthropicModels = models.filter(
37
59
  (m) => m.provider === "anthropic"
@@ -83,6 +105,25 @@ export function ChatModelSelector({
83
105
  </DropdownMenuGroup>
84
106
  </>
85
107
  )}
108
+
109
+ {ollamaModels.length > 0 && (
110
+ <>
111
+ <DropdownMenuSeparator />
112
+ <DropdownMenuLabel className="text-xs text-muted-foreground">
113
+ Ollama (Local)
114
+ </DropdownMenuLabel>
115
+ <DropdownMenuGroup>
116
+ {ollamaModels.map((m) => (
117
+ <ModelMenuItem
118
+ key={m.id}
119
+ model={m}
120
+ isSelected={m.id === modelId}
121
+ onSelect={onModelChange}
122
+ />
123
+ ))}
124
+ </DropdownMenuGroup>
125
+ </>
126
+ )}
86
127
  </DropdownMenuContent>
87
128
  </DropdownMenu>
88
129
  );
@@ -515,6 +515,7 @@ export function ChatShell({
515
515
  modelId={modelId}
516
516
  onModelChange={handleModelChange}
517
517
  availableModels={availableModels}
518
+ projectId={activeConversation?.projectId}
518
519
  />
519
520
  </ChatEmptyState>
520
521
  </div>
@@ -539,6 +540,7 @@ export function ChatShell({
539
540
  modelId={modelId}
540
541
  onModelChange={handleModelChange}
541
542
  availableModels={availableModels}
543
+ projectId={activeConversation?.projectId}
542
544
  />
543
545
  </>
544
546
  )}
@@ -1,22 +1,22 @@
1
1
  import Link from "next/link";
2
2
  import { Button } from "@/components/ui/button";
3
- import { Shield, GitBranch, Wallet } from "lucide-react";
3
+ import { Shield, Zap, Wallet } from "lucide-react";
4
4
 
5
5
  const pillars = [
6
6
  {
7
7
  icon: Shield,
8
- title: "Governed Execution",
9
- description: "Every agent action requires explicit approval. Full audit trail for every decision.",
8
+ title: "Your Rules, Enforced",
9
+ description: "Every agent action respects your policies. Full audit trail for every decision.",
10
10
  },
11
11
  {
12
- icon: GitBranch,
13
- title: "Reusable Automation",
14
- description: "Build workflow templates once, run them many times. Profile-aware agent routing.",
12
+ icon: Zap,
13
+ title: "Business on Autopilot",
14
+ description: "Build workflow templates once, run them many times. 21 specialist profiles ready to deploy.",
15
15
  },
16
16
  {
17
17
  icon: Wallet,
18
- title: "Cost & Visibility",
19
- description: "Track spend per task, per runtime. Budget guardrails prevent surprise bills.",
18
+ title: "Know What You Spend",
19
+ description: "Track spend per task, per provider. Budget guardrails prevent surprise bills.",
20
20
  },
21
21
  ];
22
22
 
@@ -31,7 +31,7 @@ export function WelcomeLanding() {
31
31
  Welcome to Stagent
32
32
  </h1>
33
33
  <p className="text-base text-muted-foreground mb-8 max-w-lg">
34
- The governed AI agent operations workspace. Execute tasks, manage workflows, and maintain full oversight of your AI agents.
34
+ Your AI Business Operating System. Deploy AI agents, automate business processes, and maintain full control of spend and execution.
35
35
  </p>
36
36
 
37
37
  <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 w-full mb-8">
@@ -2,6 +2,8 @@
2
2
 
3
3
  import { Card, CardContent } from "@/components/ui/card";
4
4
  import { Badge } from "@/components/ui/badge";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Bot, Link as LinkIcon } from "lucide-react";
5
7
  import type { EnvironmentArtifactRow } from "@/lib/db/schema";
6
8
  import { CATEGORY_META } from "./summary-cards-row";
7
9
  import { PersonaIndicator } from "./persona-indicator";
@@ -9,6 +11,7 @@ import { PersonaIndicator } from "./persona-indicator";
9
11
  interface ArtifactCardProps {
10
12
  artifact: EnvironmentArtifactRow;
11
13
  onClick: () => void;
14
+ onCreateProfile?: () => void;
12
15
  }
13
16
 
14
17
  function formatSize(bytes: number): string {
@@ -17,7 +20,7 @@ function formatSize(bytes: number): string {
17
20
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
18
21
  }
19
22
 
20
- export function ArtifactCard({ artifact, onClick }: ArtifactCardProps) {
23
+ export function ArtifactCard({ artifact, onClick, onCreateProfile }: ArtifactCardProps) {
21
24
  const meta = CATEGORY_META[artifact.category];
22
25
  const Icon = meta?.icon;
23
26
 
@@ -57,10 +60,33 @@ export function ArtifactCard({ artifact, onClick }: ArtifactCardProps) {
57
60
  <Badge variant="secondary" className="text-[10px] px-1.5 py-0">
58
61
  {artifact.scope}
59
62
  </Badge>
63
+ {/* Profile linkage indicator for skill artifacts */}
64
+ {artifact.category === "skill" && artifact.linkedProfileId && (
65
+ <Badge variant="default" className="text-[10px] px-1.5 py-0 gap-0.5">
66
+ <LinkIcon className="h-2.5 w-2.5" />
67
+ Profile
68
+ </Badge>
69
+ )}
60
70
  <span className="text-[10px] text-muted-foreground ml-auto">
61
71
  {formatSize(artifact.sizeBytes)}
62
72
  </span>
63
73
  </div>
74
+
75
+ {/* Create Profile button for unlinked skill artifacts */}
76
+ {artifact.category === "skill" && !artifact.linkedProfileId && onCreateProfile && (
77
+ <Button
78
+ variant="outline"
79
+ size="sm"
80
+ className="w-full text-xs gap-1.5"
81
+ onClick={(e) => {
82
+ e.stopPropagation();
83
+ onCreateProfile();
84
+ }}
85
+ >
86
+ <Bot className="h-3 w-3" />
87
+ Create Profile
88
+ </Button>
89
+ )}
64
90
  </CardContent>
65
91
  </Card>
66
92
  );