stagent 0.4.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 (276) hide show
  1. package/README.md +67 -31
  2. package/dist/cli.js +151 -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 +53 -71
  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 +77 -41
  21. package/docs/features/settings.md +134 -51
  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 +79 -47
  32. package/docs/superpowers/plans/2026-03-30-finish-in-progress-features.md +547 -0
  33. package/docs/superpowers/specs/2026-03-27-chat-screenshot-display-design.md +303 -0
  34. package/docs/use-cases/agency-operator.md +84 -0
  35. package/docs/use-cases/solo-founder.md +75 -0
  36. package/docs/why-stagent.md +59 -0
  37. package/package.json +12 -3
  38. package/src/app/api/channels/[id]/route.ts +103 -0
  39. package/src/app/api/channels/[id]/test/route.ts +52 -0
  40. package/src/app/api/channels/inbound/slack/route.ts +109 -0
  41. package/src/app/api/channels/inbound/telegram/poll/route.ts +128 -0
  42. package/src/app/api/channels/inbound/telegram/route.ts +76 -0
  43. package/src/app/api/channels/route.ts +71 -0
  44. package/src/app/api/chat/conversations/[id]/messages/route.ts +3 -2
  45. package/src/app/api/chat/conversations/route.ts +15 -0
  46. package/src/app/api/chat/entities/search/route.ts +112 -0
  47. package/src/app/api/documents/[id]/file/route.ts +4 -1
  48. package/src/app/api/environment/profiles/suggest/route.ts +19 -3
  49. package/src/app/api/environment/scan/route.ts +8 -1
  50. package/src/app/api/handoffs/[id]/route.ts +76 -0
  51. package/src/app/api/handoffs/route.ts +89 -0
  52. package/src/app/api/memory/route.ts +181 -0
  53. package/src/app/api/profiles/[id]/route.ts +16 -1
  54. package/src/app/api/profiles/[id]/test/route.ts +4 -0
  55. package/src/app/api/profiles/[id]/test-results/route.ts +22 -0
  56. package/src/app/api/profiles/[id]/test-single/route.ts +64 -0
  57. package/src/app/api/profiles/assist/route.ts +35 -0
  58. package/src/app/api/profiles/import-repo/apply-updates/route.ts +123 -0
  59. package/src/app/api/profiles/import-repo/check-updates/route.ts +163 -0
  60. package/src/app/api/profiles/import-repo/confirm/route.ts +118 -0
  61. package/src/app/api/profiles/import-repo/preview/route.ts +107 -0
  62. package/src/app/api/profiles/import-repo/route.ts +29 -0
  63. package/src/app/api/profiles/import-repo/scan/route.ts +25 -0
  64. package/src/app/api/profiles/route.ts +73 -22
  65. package/src/app/api/projects/[id]/route.ts +119 -9
  66. package/src/app/api/projects/__tests__/delete-project.test.ts +170 -0
  67. package/src/app/api/runtimes/ollama/route.ts +86 -0
  68. package/src/app/api/runtimes/suggest/route.ts +29 -0
  69. package/src/app/api/schedules/[id]/heartbeat-history/route.ts +77 -0
  70. package/src/app/api/schedules/[id]/route.ts +41 -3
  71. package/src/app/api/schedules/parse/route.ts +66 -0
  72. package/src/app/api/schedules/route.ts +71 -12
  73. package/src/app/api/settings/author-default/route.ts +7 -0
  74. package/src/app/api/settings/browser-tools/route.ts +68 -0
  75. package/src/app/api/settings/learning/route.ts +41 -0
  76. package/src/app/api/settings/ollama/route.ts +34 -0
  77. package/src/app/api/settings/providers/route.ts +57 -0
  78. package/src/app/api/settings/routing/route.ts +24 -0
  79. package/src/app/api/settings/web-search/route.ts +28 -0
  80. package/src/app/api/tasks/[id]/execute/route.ts +13 -1
  81. package/src/app/documents/page.tsx +3 -0
  82. package/src/app/environment/page.tsx +8 -1
  83. package/src/app/settings/page.tsx +12 -4
  84. package/src/app/workflows/[id]/edit/page.tsx +2 -0
  85. package/src/app/workflows/new/page.tsx +2 -0
  86. package/src/components/chat/chat-command-popover.tsx +280 -0
  87. package/src/components/chat/chat-input.tsx +90 -10
  88. package/src/components/chat/chat-message.tsx +9 -3
  89. package/src/components/chat/chat-model-selector.tsx +42 -1
  90. package/src/components/chat/chat-shell.tsx +31 -5
  91. package/src/components/chat/screenshot-gallery.tsx +96 -0
  92. package/src/components/dashboard/welcome-landing.tsx +9 -9
  93. package/src/components/environment/artifact-card.tsx +27 -1
  94. package/src/components/environment/environment-dashboard.tsx +50 -2
  95. package/src/components/environment/environment-summary-card.tsx +5 -2
  96. package/src/components/environment/suggested-profiles.tsx +117 -52
  97. package/src/components/handoffs/handoff-approval-card.tsx +159 -0
  98. package/src/components/memory/memory-browser.tsx +315 -0
  99. package/src/components/monitoring/log-entry.tsx +61 -27
  100. package/src/components/profiles/learned-context-panel.tsx +4 -4
  101. package/src/components/profiles/profile-assist-panel.tsx +512 -0
  102. package/src/components/profiles/profile-browser.tsx +109 -8
  103. package/src/components/profiles/profile-card.tsx +29 -1
  104. package/src/components/profiles/profile-detail-view.tsx +200 -28
  105. package/src/components/profiles/profile-form-view.tsx +220 -82
  106. package/src/components/profiles/repo-import-wizard.tsx +648 -0
  107. package/src/components/profiles/smoke-test-editor.tsx +106 -0
  108. package/src/components/projects/project-detail.tsx +15 -2
  109. package/src/components/schedules/schedule-create-sheet.tsx +32 -330
  110. package/src/components/schedules/schedule-detail-sheet.tsx +37 -21
  111. package/src/components/schedules/schedule-edit-sheet.tsx +159 -0
  112. package/src/components/schedules/schedule-form.tsx +749 -0
  113. package/src/components/schedules/schedule-list.tsx +31 -2
  114. package/src/components/settings/auth-method-selector.tsx +7 -1
  115. package/src/components/settings/browser-tools-section.tsx +247 -0
  116. package/src/components/settings/budget-guardrails-section.tsx +111 -48
  117. package/src/components/settings/channels-section.tsx +526 -0
  118. package/src/components/settings/chat-settings-section.tsx +27 -1
  119. package/src/components/settings/data-management-section.tsx +8 -6
  120. package/src/components/settings/learning-context-section.tsx +124 -0
  121. package/src/components/settings/ollama-section.tsx +270 -0
  122. package/src/components/settings/providers-runtimes-section.tsx +499 -0
  123. package/src/components/settings/runtime-timeout-section.tsx +4 -4
  124. package/src/components/settings/web-search-section.tsx +101 -0
  125. package/src/components/shared/command-palette.tsx +1 -30
  126. package/src/components/shared/screenshot-lightbox.tsx +151 -0
  127. package/src/components/shared/tag-input.tsx +156 -0
  128. package/src/components/tasks/kanban-board.tsx +32 -0
  129. package/src/components/tasks/kanban-column.tsx +4 -2
  130. package/src/components/tasks/task-card.tsx +1 -0
  131. package/src/components/tasks/task-chip-bar.tsx +6 -1
  132. package/src/components/tasks/task-create-panel.tsx +55 -5
  133. package/src/components/workflows/workflow-form-view.tsx +38 -3
  134. package/src/hooks/use-caret-position.ts +104 -0
  135. package/src/hooks/use-chat-autocomplete.ts +288 -0
  136. package/src/hooks/use-project-skills.ts +66 -0
  137. package/src/hooks/use-tag-suggestions.ts +31 -0
  138. package/src/instrumentation.ts +4 -1
  139. package/src/lib/agents/__tests__/browser-mcp.test.ts +175 -0
  140. package/src/lib/agents/__tests__/claude-agent.test.ts +6 -0
  141. package/src/lib/agents/__tests__/learned-context.test.ts +10 -0
  142. package/src/lib/agents/agentic-loop.ts +235 -0
  143. package/src/lib/agents/browser-mcp.ts +174 -0
  144. package/src/lib/agents/claude-agent.ts +83 -198
  145. package/src/lib/agents/handoff/bus.ts +164 -0
  146. package/src/lib/agents/handoff/governance.ts +47 -0
  147. package/src/lib/agents/handoff/types.ts +16 -0
  148. package/src/lib/agents/learned-context.ts +27 -7
  149. package/src/lib/agents/memory/decay.ts +61 -0
  150. package/src/lib/agents/memory/extractor.ts +181 -0
  151. package/src/lib/agents/memory/retrieval.ts +96 -0
  152. package/src/lib/agents/memory/types.ts +6 -0
  153. package/src/lib/agents/profiles/__tests__/project-profiles.test.ts +119 -0
  154. package/src/lib/agents/profiles/__tests__/registry.test.ts +11 -3
  155. package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +2 -2
  156. package/src/lib/agents/profiles/builtins/content-creator/SKILL.md +19 -0
  157. package/src/lib/agents/profiles/builtins/content-creator/profile.yaml +27 -0
  158. package/src/lib/agents/profiles/builtins/customer-support-agent/SKILL.md +19 -0
  159. package/src/lib/agents/profiles/builtins/customer-support-agent/profile.yaml +26 -0
  160. package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +2 -2
  161. package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +2 -2
  162. package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +2 -2
  163. package/src/lib/agents/profiles/builtins/financial-analyst/SKILL.md +19 -0
  164. package/src/lib/agents/profiles/builtins/financial-analyst/profile.yaml +24 -0
  165. package/src/lib/agents/profiles/builtins/general/profile.yaml +2 -2
  166. package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +2 -2
  167. package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +2 -2
  168. package/src/lib/agents/profiles/builtins/marketing-strategist/SKILL.md +19 -0
  169. package/src/lib/agents/profiles/builtins/marketing-strategist/profile.yaml +27 -0
  170. package/src/lib/agents/profiles/builtins/operations-coordinator/SKILL.md +19 -0
  171. package/src/lib/agents/profiles/builtins/operations-coordinator/profile.yaml +26 -0
  172. package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +2 -2
  173. package/src/lib/agents/profiles/builtins/researcher/SKILL.md +1 -0
  174. package/src/lib/agents/profiles/builtins/researcher/profile.yaml +2 -2
  175. package/src/lib/agents/profiles/builtins/sales-researcher/SKILL.md +19 -0
  176. package/src/lib/agents/profiles/builtins/sales-researcher/profile.yaml +26 -0
  177. package/src/lib/agents/profiles/builtins/shopping-assistant/SKILL.md +1 -0
  178. package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +2 -2
  179. package/src/lib/agents/profiles/builtins/sweep/profile.yaml +1 -1
  180. package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +2 -2
  181. package/src/lib/agents/profiles/builtins/travel-planner/SKILL.md +2 -0
  182. package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +2 -2
  183. package/src/lib/agents/profiles/builtins/wealth-manager/SKILL.md +2 -0
  184. package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +2 -2
  185. package/src/lib/agents/profiles/project-profiles.ts +193 -0
  186. package/src/lib/agents/profiles/registry.ts +130 -6
  187. package/src/lib/agents/profiles/types.ts +28 -0
  188. package/src/lib/agents/router.ts +174 -2
  189. package/src/lib/agents/runtime/__tests__/catalog.test.ts +15 -4
  190. package/src/lib/agents/runtime/anthropic-direct.ts +644 -0
  191. package/src/lib/agents/runtime/catalog.ts +57 -2
  192. package/src/lib/agents/runtime/claude.ts +205 -1
  193. package/src/lib/agents/runtime/index.ts +22 -0
  194. package/src/lib/agents/runtime/ollama-adapter.ts +409 -0
  195. package/src/lib/agents/runtime/openai-direct.ts +514 -0
  196. package/src/lib/agents/runtime/profile-assist-types.ts +30 -0
  197. package/src/lib/agents/runtime/types.ts +2 -0
  198. package/src/lib/agents/tool-permissions.ts +203 -0
  199. package/src/lib/channels/gateway.ts +321 -0
  200. package/src/lib/channels/poller.ts +268 -0
  201. package/src/lib/channels/registry.ts +90 -0
  202. package/src/lib/channels/slack-adapter.ts +188 -0
  203. package/src/lib/channels/telegram-adapter.ts +218 -0
  204. package/src/lib/channels/types.ts +43 -0
  205. package/src/lib/channels/webhook-adapter.ts +74 -0
  206. package/src/lib/chat/command-data.ts +50 -0
  207. package/src/lib/chat/context-builder.ts +147 -3
  208. package/src/lib/chat/engine.ts +182 -19
  209. package/src/lib/chat/ollama-engine.ts +198 -0
  210. package/src/lib/chat/slash-commands.ts +191 -0
  211. package/src/lib/chat/stagent-tools.ts +106 -20
  212. package/src/lib/chat/tool-catalog.ts +209 -0
  213. package/src/lib/chat/tool-registry.ts +90 -0
  214. package/src/lib/chat/tools/chat-history-tools.ts +4 -4
  215. package/src/lib/chat/tools/document-tools.ts +43 -6
  216. package/src/lib/chat/tools/handoff-tools.ts +70 -0
  217. package/src/lib/chat/tools/notification-tools.ts +4 -4
  218. package/src/lib/chat/tools/profile-tools.ts +3 -3
  219. package/src/lib/chat/tools/project-tools.ts +3 -3
  220. package/src/lib/chat/tools/schedule-tools.ts +29 -13
  221. package/src/lib/chat/tools/settings-tools.ts +2 -2
  222. package/src/lib/chat/tools/task-tools.ts +66 -11
  223. package/src/lib/chat/tools/usage-tools.ts +2 -2
  224. package/src/lib/chat/tools/workflow-tools.ts +8 -8
  225. package/src/lib/chat/types.ts +22 -6
  226. package/src/lib/constants/known-tools.ts +19 -0
  227. package/src/lib/constants/prose-styles.ts +1 -1
  228. package/src/lib/constants/settings.ts +11 -0
  229. package/src/lib/data/channel-bindings.ts +85 -0
  230. package/src/lib/data/clear.ts +38 -4
  231. package/src/lib/data/profile-test-results.ts +48 -0
  232. package/src/lib/data/seed-data/conversations.ts +196 -0
  233. package/src/lib/data/seed-data/learned-context.ts +99 -0
  234. package/src/lib/data/seed-data/notifications.ts +54 -1
  235. package/src/lib/data/seed-data/profile-test-results.ts +96 -0
  236. package/src/lib/data/seed-data/repo-imports.ts +51 -0
  237. package/src/lib/data/seed-data/views.ts +60 -0
  238. package/src/lib/data/seed.ts +51 -0
  239. package/src/lib/db/bootstrap.ts +167 -0
  240. package/src/lib/db/migrations/0012_add_screenshot_columns.sql +5 -0
  241. package/src/lib/db/migrations/0013_add_repo_imports.sql +15 -0
  242. package/src/lib/db/migrations/0014_add_linked_profile_id.sql +3 -0
  243. package/src/lib/db/migrations/0015_add_channel_bindings.sql +23 -0
  244. package/src/lib/db/schema.ts +192 -1
  245. package/src/lib/environment/__tests__/auto-scan.test.ts +86 -0
  246. package/src/lib/environment/__tests__/profile-linker.test.ts +187 -0
  247. package/src/lib/environment/auto-scan.ts +48 -0
  248. package/src/lib/environment/data.ts +25 -0
  249. package/src/lib/environment/profile-generator.ts +40 -10
  250. package/src/lib/environment/profile-linker.ts +143 -0
  251. package/src/lib/environment/profile-rules.ts +96 -0
  252. package/src/lib/import/dedup.ts +149 -0
  253. package/src/lib/import/format-adapter.ts +631 -0
  254. package/src/lib/import/github-api.ts +219 -0
  255. package/src/lib/import/repo-scanner.ts +251 -0
  256. package/src/lib/schedules/__tests__/nlp-parser.test.ts +330 -0
  257. package/src/lib/schedules/active-hours.ts +120 -0
  258. package/src/lib/schedules/heartbeat-parser.ts +224 -0
  259. package/src/lib/schedules/heartbeat-prompt.ts +153 -0
  260. package/src/lib/schedules/nlp-parser.ts +357 -0
  261. package/src/lib/schedules/scheduler.ts +218 -3
  262. package/src/lib/screenshots/__tests__/persist.test.ts +104 -0
  263. package/src/lib/screenshots/persist.ts +114 -0
  264. package/src/lib/settings/__tests__/budget-guardrails.test.ts +39 -1
  265. package/src/lib/settings/helpers.ts +6 -0
  266. package/src/lib/settings/routing.ts +24 -0
  267. package/src/lib/settings/runtime-setup.ts +28 -1
  268. package/src/lib/usage/ledger.ts +2 -1
  269. package/src/lib/utils/stagent-paths.ts +4 -0
  270. package/src/lib/validators/__tests__/settings.test.ts +9 -0
  271. package/src/lib/validators/profile.ts +39 -0
  272. package/src/lib/workflows/blueprints/builtins/business-daily-briefing.yaml +102 -0
  273. package/src/lib/workflows/blueprints/builtins/content-marketing-pipeline.yaml +90 -0
  274. package/src/lib/workflows/blueprints/builtins/customer-support-triage.yaml +107 -0
  275. package/src/lib/workflows/blueprints/builtins/financial-reporting.yaml +104 -0
  276. package/src/lib/workflows/blueprints/builtins/lead-research-pipeline.yaml +82 -0
@@ -3,6 +3,7 @@ import { db } from "@/lib/db";
3
3
  import { schedules } from "@/lib/db/schema";
4
4
  import { desc, eq } from "drizzle-orm";
5
5
  import { parseInterval, computeNextFireTime } from "@/lib/schedules/interval-parser";
6
+ import { parseNaturalLanguage } from "@/lib/schedules/nlp-parser";
6
7
  import { resolveAgentRuntime } from "@/lib/agents/runtime/catalog";
7
8
  import { validateRuntimeProfileAssignment } from "@/lib/agents/profiles/assignment-validation";
8
9
 
@@ -27,6 +28,12 @@ export async function POST(req: NextRequest) {
27
28
  recurs,
28
29
  maxFirings,
29
30
  expiresInHours,
31
+ type,
32
+ heartbeatChecklist,
33
+ activeHoursStart,
34
+ activeHoursEnd,
35
+ activeTimezone,
36
+ heartbeatBudgetPerDay,
30
37
  } =
31
38
  body as {
32
39
  name?: string;
@@ -38,27 +45,64 @@ export async function POST(req: NextRequest) {
38
45
  recurs?: boolean;
39
46
  maxFirings?: number;
40
47
  expiresInHours?: number;
48
+ type?: "scheduled" | "heartbeat";
49
+ heartbeatChecklist?: Array<{ id: string; instruction: string; priority: string }>;
50
+ activeHoursStart?: number;
51
+ activeHoursEnd?: number;
52
+ activeTimezone?: string;
53
+ heartbeatBudgetPerDay?: number;
41
54
  };
42
55
 
56
+ const scheduleType = type ?? "scheduled";
57
+
43
58
  if (!name?.trim()) {
44
59
  return NextResponse.json({ error: "Name is required" }, { status: 400 });
45
60
  }
46
- if (!prompt?.trim()) {
47
- return NextResponse.json({ error: "Prompt is required" }, { status: 400 });
48
- }
49
61
  if (!interval?.trim()) {
50
62
  return NextResponse.json({ error: "Interval is required" }, { status: 400 });
51
63
  }
52
64
 
53
- // Parse interval into cron expression
65
+ // Heartbeat-specific validation
66
+ if (scheduleType === "heartbeat") {
67
+ if (!heartbeatChecklist || heartbeatChecklist.length === 0) {
68
+ return NextResponse.json(
69
+ { error: "Heartbeat schedules require at least one checklist item" },
70
+ { status: 400 }
71
+ );
72
+ }
73
+ if (activeHoursStart !== undefined && (activeHoursStart < 0 || activeHoursStart > 23)) {
74
+ return NextResponse.json(
75
+ { error: "Active hours start must be 0-23" },
76
+ { status: 400 }
77
+ );
78
+ }
79
+ if (activeHoursEnd !== undefined && (activeHoursEnd < 0 || activeHoursEnd > 23)) {
80
+ return NextResponse.json(
81
+ { error: "Active hours end must be 0-23" },
82
+ { status: 400 }
83
+ );
84
+ }
85
+ }
86
+
87
+ // For heartbeat type, prompt is optional (auto-generated from checklist)
88
+ if (scheduleType === "scheduled" && !prompt?.trim()) {
89
+ return NextResponse.json({ error: "Prompt is required" }, { status: 400 });
90
+ }
91
+
92
+ // Parse interval into cron expression — try NLP first, then shorthand/cron
54
93
  let cronExpression: string;
55
- try {
56
- cronExpression = parseInterval(interval);
57
- } catch (err) {
58
- return NextResponse.json(
59
- { error: (err as Error).message },
60
- { status: 400 }
61
- );
94
+ const nlResult = parseNaturalLanguage(interval);
95
+ if (nlResult) {
96
+ cronExpression = nlResult.cronExpression;
97
+ } else {
98
+ try {
99
+ cronExpression = parseInterval(interval);
100
+ } catch (err) {
101
+ return NextResponse.json(
102
+ { error: (err as Error).message },
103
+ { status: 400 }
104
+ );
105
+ }
62
106
  }
63
107
 
64
108
  if (assignedAgent !== undefined && assignedAgent !== null && assignedAgent !== "") {
@@ -90,10 +134,15 @@ export async function POST(req: NextRequest) {
90
134
  ? new Date(now.getTime() + expiresInHours * 60 * 60 * 1000)
91
135
  : null;
92
136
 
137
+ // For heartbeat schedules, use a placeholder prompt (actual prompt is built at runtime from checklist)
138
+ const effectivePrompt = scheduleType === "heartbeat"
139
+ ? (prompt?.trim() || `Heartbeat check: ${name?.trim()}`)
140
+ : prompt!.trim();
141
+
93
142
  await db.insert(schedules).values({
94
143
  id,
95
144
  name: name.trim(),
96
- prompt: prompt.trim(),
145
+ prompt: effectivePrompt,
97
146
  cronExpression,
98
147
  projectId: projectId || null,
99
148
  assignedAgent: assignedAgent || null,
@@ -104,6 +153,16 @@ export async function POST(req: NextRequest) {
104
153
  firingCount: 0,
105
154
  expiresAt,
106
155
  nextFireAt,
156
+ type: scheduleType,
157
+ heartbeatChecklist: heartbeatChecklist ? JSON.stringify(heartbeatChecklist) : null,
158
+ activeHoursStart: activeHoursStart ?? null,
159
+ activeHoursEnd: activeHoursEnd ?? null,
160
+ activeTimezone: activeTimezone ?? "UTC",
161
+ suppressionCount: 0,
162
+ lastActionAt: null,
163
+ heartbeatBudgetPerDay: heartbeatBudgetPerDay ?? null,
164
+ heartbeatSpentToday: 0,
165
+ heartbeatBudgetResetAt: null,
107
166
  createdAt: now,
108
167
  updatedAt: now,
109
168
  });
@@ -0,0 +1,7 @@
1
+ import { NextResponse } from "next/server";
2
+ import os from "os";
3
+
4
+ export async function GET() {
5
+ const info = os.userInfo();
6
+ return NextResponse.json({ author: info.username });
7
+ }
@@ -0,0 +1,68 @@
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 [chromeEnabled, playwrightEnabled, chromeConfig, playwrightConfig] =
7
+ await Promise.all([
8
+ getSetting(SETTINGS_KEYS.BROWSER_MCP_CHROME_DEVTOOLS_ENABLED),
9
+ getSetting(SETTINGS_KEYS.BROWSER_MCP_PLAYWRIGHT_ENABLED),
10
+ getSetting(SETTINGS_KEYS.BROWSER_MCP_CHROME_DEVTOOLS_CONFIG),
11
+ getSetting(SETTINGS_KEYS.BROWSER_MCP_PLAYWRIGHT_CONFIG),
12
+ ]);
13
+
14
+ return NextResponse.json({
15
+ chromeDevtoolsEnabled: chromeEnabled === "true",
16
+ playwrightEnabled: playwrightEnabled === "true",
17
+ chromeDevtoolsConfig: chromeConfig ?? "",
18
+ playwrightConfig: playwrightConfig ?? "",
19
+ });
20
+ }
21
+
22
+ export async function POST(req: NextRequest) {
23
+ const body = await req.json();
24
+
25
+ if (body.chromeDevtoolsEnabled !== undefined) {
26
+ await setSetting(
27
+ SETTINGS_KEYS.BROWSER_MCP_CHROME_DEVTOOLS_ENABLED,
28
+ body.chromeDevtoolsEnabled ? "true" : "false"
29
+ );
30
+ }
31
+
32
+ if (body.playwrightEnabled !== undefined) {
33
+ await setSetting(
34
+ SETTINGS_KEYS.BROWSER_MCP_PLAYWRIGHT_ENABLED,
35
+ body.playwrightEnabled ? "true" : "false"
36
+ );
37
+ }
38
+
39
+ if (body.chromeDevtoolsConfig !== undefined) {
40
+ await setSetting(
41
+ SETTINGS_KEYS.BROWSER_MCP_CHROME_DEVTOOLS_CONFIG,
42
+ body.chromeDevtoolsConfig
43
+ );
44
+ }
45
+
46
+ if (body.playwrightConfig !== undefined) {
47
+ await setSetting(
48
+ SETTINGS_KEYS.BROWSER_MCP_PLAYWRIGHT_CONFIG,
49
+ body.playwrightConfig
50
+ );
51
+ }
52
+
53
+ // Return updated state
54
+ const [chromeEnabled, playwrightEnabled, chromeConfig, playwrightConfig] =
55
+ await Promise.all([
56
+ getSetting(SETTINGS_KEYS.BROWSER_MCP_CHROME_DEVTOOLS_ENABLED),
57
+ getSetting(SETTINGS_KEYS.BROWSER_MCP_PLAYWRIGHT_ENABLED),
58
+ getSetting(SETTINGS_KEYS.BROWSER_MCP_CHROME_DEVTOOLS_CONFIG),
59
+ getSetting(SETTINGS_KEYS.BROWSER_MCP_PLAYWRIGHT_CONFIG),
60
+ ]);
61
+
62
+ return NextResponse.json({
63
+ chromeDevtoolsEnabled: chromeEnabled === "true",
64
+ playwrightEnabled: playwrightEnabled === "true",
65
+ chromeDevtoolsConfig: chromeConfig ?? "",
66
+ playwrightConfig: playwrightConfig ?? "",
67
+ });
68
+ }
@@ -0,0 +1,41 @@
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 contextCharLimit = await getSetting(
7
+ SETTINGS_KEYS.LEARNING_CONTEXT_CHAR_LIMIT
8
+ );
9
+ return NextResponse.json({
10
+ contextCharLimit: contextCharLimit ?? "8000",
11
+ });
12
+ }
13
+
14
+ export async function POST(req: NextRequest) {
15
+ const body = await req.json();
16
+
17
+ if (body.contextCharLimit !== undefined) {
18
+ const limit = parseInt(body.contextCharLimit, 10);
19
+ if (isNaN(limit) || limit < 2000 || limit > 32000 || limit % 1000 !== 0) {
20
+ return NextResponse.json(
21
+ {
22
+ error:
23
+ "contextCharLimit must be between 2,000 and 32,000 (step 1,000)",
24
+ },
25
+ { status: 400 }
26
+ );
27
+ }
28
+ await setSetting(
29
+ SETTINGS_KEYS.LEARNING_CONTEXT_CHAR_LIMIT,
30
+ String(limit)
31
+ );
32
+ }
33
+
34
+ const contextCharLimit = await getSetting(
35
+ SETTINGS_KEYS.LEARNING_CONTEXT_CHAR_LIMIT
36
+ );
37
+
38
+ return NextResponse.json({
39
+ contextCharLimit: contextCharLimit ?? "8000",
40
+ });
41
+ }
@@ -0,0 +1,34 @@
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
+ /**
6
+ * GET /api/settings/ollama — Read Ollama settings.
7
+ */
8
+ export async function GET() {
9
+ const baseUrl = await getSetting(SETTINGS_KEYS.OLLAMA_BASE_URL);
10
+ const defaultModel = await getSetting(SETTINGS_KEYS.OLLAMA_DEFAULT_MODEL);
11
+
12
+ return NextResponse.json({
13
+ baseUrl: baseUrl || "http://localhost:11434",
14
+ defaultModel: defaultModel || "",
15
+ });
16
+ }
17
+
18
+ /**
19
+ * POST /api/settings/ollama — Update Ollama settings.
20
+ * Body: { baseUrl?: string, defaultModel?: string }
21
+ */
22
+ export async function POST(req: NextRequest) {
23
+ const body = await req.json();
24
+
25
+ if (body.baseUrl !== undefined) {
26
+ await setSetting(SETTINGS_KEYS.OLLAMA_BASE_URL, body.baseUrl);
27
+ }
28
+
29
+ if (body.defaultModel !== undefined) {
30
+ await setSetting(SETTINGS_KEYS.OLLAMA_DEFAULT_MODEL, body.defaultModel);
31
+ }
32
+
33
+ return NextResponse.json({ ok: true });
34
+ }
@@ -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,10 +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";
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";
8
12
  import { PageShell } from "@/components/shared/page-shell";
9
13
 
10
14
  export const dynamic = "force-dynamic";
@@ -13,10 +17,14 @@ export default function SettingsPage() {
13
17
  return (
14
18
  <PageShell title="Settings" description="Manage your Stagent configuration">
15
19
  <div className="space-y-6">
16
- <AuthConfigSection />
17
- <OpenAIRuntimeSection />
20
+ <ProvidersAndRuntimesSection />
21
+ <OllamaSection />
18
22
  <ChatSettingsSection />
19
23
  <RuntimeTimeoutSection />
24
+ <LearningContextSection />
25
+ <WebSearchSection />
26
+ <BrowserToolsSection />
27
+ <ChannelsSection />
20
28
  <BudgetGuardrailsSection />
21
29
  <PermissionsSections />
22
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 (