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,526 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState, useCallback } from "react";
4
+ import {
5
+ Send,
6
+ Plus,
7
+ Trash2,
8
+ Zap,
9
+ MessageSquare,
10
+ Globe,
11
+ CheckCircle2,
12
+ XCircle,
13
+ MinusCircle,
14
+ Loader2,
15
+ } from "lucide-react";
16
+ import { toast } from "sonner";
17
+ import {
18
+ Card,
19
+ CardContent,
20
+ CardDescription,
21
+ CardHeader,
22
+ CardTitle,
23
+ } from "@/components/ui/card";
24
+ import { Button } from "@/components/ui/button";
25
+ import { Input } from "@/components/ui/input";
26
+ import { Label } from "@/components/ui/label";
27
+ import { Switch } from "@/components/ui/switch";
28
+ import { Badge } from "@/components/ui/badge";
29
+ import {
30
+ Dialog,
31
+ DialogContent,
32
+ DialogDescription,
33
+ DialogFooter,
34
+ DialogHeader,
35
+ DialogTitle,
36
+ DialogTrigger,
37
+ } from "@/components/ui/dialog";
38
+ import {
39
+ Select,
40
+ SelectContent,
41
+ SelectItem,
42
+ SelectTrigger,
43
+ SelectValue,
44
+ } from "@/components/ui/select";
45
+
46
+ interface ChannelConfig {
47
+ id: string;
48
+ channelType: "slack" | "telegram" | "webhook";
49
+ name: string;
50
+ config: string;
51
+ status: "active" | "disabled";
52
+ testStatus: "untested" | "ok" | "failed";
53
+ direction: "outbound" | "bidirectional";
54
+ createdAt: string;
55
+ updatedAt: string;
56
+ }
57
+
58
+ const CHANNEL_ICONS: Record<string, typeof Send> = {
59
+ slack: Zap,
60
+ telegram: MessageSquare,
61
+ webhook: Globe,
62
+ };
63
+
64
+ const TEST_STATUS_ICONS: Record<string, typeof CheckCircle2> = {
65
+ ok: CheckCircle2,
66
+ failed: XCircle,
67
+ untested: MinusCircle,
68
+ };
69
+
70
+ const TEST_STATUS_COLORS: Record<string, string> = {
71
+ ok: "text-green-600",
72
+ failed: "text-red-600",
73
+ untested: "text-muted-foreground",
74
+ };
75
+
76
+ export function ChannelsSection() {
77
+ const [channels, setChannels] = useState<ChannelConfig[]>([]);
78
+ const [dialogOpen, setDialogOpen] = useState(false);
79
+ const [testingId, setTestingId] = useState<string | null>(null);
80
+
81
+ // Form state
82
+ const [formType, setFormType] = useState<"slack" | "telegram" | "webhook">("slack");
83
+ const [formName, setFormName] = useState("");
84
+ const [formWebhookUrl, setFormWebhookUrl] = useState("");
85
+ const [formBotToken, setFormBotToken] = useState("");
86
+ const [formChatId, setFormChatId] = useState("");
87
+ const [formUrl, setFormUrl] = useState("");
88
+ const [formHeaders, setFormHeaders] = useState("");
89
+ // Slack bidirectional fields
90
+ const [formSlackBotToken, setFormSlackBotToken] = useState("");
91
+ const [formSigningSecret, setFormSigningSecret] = useState("");
92
+ const [formSlackChannelId, setFormSlackChannelId] = useState("");
93
+ const [saving, setSaving] = useState(false);
94
+
95
+ const fetchChannels = useCallback(async () => {
96
+ try {
97
+ const res = await fetch("/api/channels");
98
+ if (res.ok) {
99
+ const data = await res.json();
100
+ setChannels(data);
101
+ }
102
+ } catch {
103
+ // Ignore
104
+ }
105
+ }, []);
106
+
107
+ useEffect(() => {
108
+ fetchChannels();
109
+ }, [fetchChannels]);
110
+
111
+ const resetForm = () => {
112
+ setFormType("slack");
113
+ setFormName("");
114
+ setFormWebhookUrl("");
115
+ setFormBotToken("");
116
+ setFormChatId("");
117
+ setFormUrl("");
118
+ setFormHeaders("");
119
+ setFormSlackBotToken("");
120
+ setFormSigningSecret("");
121
+ setFormSlackChannelId("");
122
+ };
123
+
124
+ const handleCreate = async () => {
125
+ let config: Record<string, unknown> = {};
126
+ if (formType === "slack") {
127
+ config = { webhookUrl: formWebhookUrl };
128
+ // Include bidirectional fields if provided
129
+ if (formSlackBotToken.trim()) config.botToken = formSlackBotToken.trim();
130
+ if (formSigningSecret.trim()) config.signingSecret = formSigningSecret.trim();
131
+ if (formSlackChannelId.trim()) config.slackChannelId = formSlackChannelId.trim();
132
+ } else if (formType === "telegram") {
133
+ config = { botToken: formBotToken, chatId: formChatId };
134
+ } else {
135
+ let headers: Record<string, string> | undefined;
136
+ if (formHeaders.trim()) {
137
+ try {
138
+ headers = JSON.parse(formHeaders);
139
+ } catch {
140
+ toast.error("Invalid headers JSON");
141
+ return;
142
+ }
143
+ }
144
+ config = { url: formUrl, ...(headers ? { headers } : {}) };
145
+ }
146
+
147
+ setSaving(true);
148
+ try {
149
+ const res = await fetch("/api/channels", {
150
+ method: "POST",
151
+ headers: { "Content-Type": "application/json" },
152
+ body: JSON.stringify({
153
+ channelType: formType,
154
+ name: formName,
155
+ config,
156
+ }),
157
+ });
158
+
159
+ if (res.ok) {
160
+ toast.success("Channel created");
161
+ setDialogOpen(false);
162
+ resetForm();
163
+ fetchChannels();
164
+ } else {
165
+ const data = await res.json();
166
+ toast.error(data.error ?? "Failed to create channel");
167
+ }
168
+ } catch {
169
+ toast.error("Failed to create channel");
170
+ } finally {
171
+ setSaving(false);
172
+ }
173
+ };
174
+
175
+ const handleToggle = async (id: string, currentStatus: string) => {
176
+ const newStatus = currentStatus === "active" ? "disabled" : "active";
177
+ try {
178
+ const res = await fetch(`/api/channels/${id}`, {
179
+ method: "PATCH",
180
+ headers: { "Content-Type": "application/json" },
181
+ body: JSON.stringify({ status: newStatus }),
182
+ });
183
+ if (res.ok) {
184
+ toast.success(`Channel ${newStatus === "active" ? "enabled" : "disabled"}`);
185
+ fetchChannels();
186
+ }
187
+ } catch {
188
+ toast.error("Failed to update channel");
189
+ }
190
+ };
191
+
192
+ const handleTest = async (id: string) => {
193
+ setTestingId(id);
194
+ try {
195
+ const res = await fetch(`/api/channels/${id}/test`, { method: "POST" });
196
+ const data = await res.json();
197
+ if (data.testStatus === "ok") {
198
+ toast.success("Connection test passed");
199
+ } else {
200
+ toast.error(`Test failed: ${data.error}`);
201
+ }
202
+ fetchChannels();
203
+ } catch {
204
+ toast.error("Failed to test channel");
205
+ } finally {
206
+ setTestingId(null);
207
+ }
208
+ };
209
+
210
+ const handleDelete = async (id: string) => {
211
+ try {
212
+ const res = await fetch(`/api/channels/${id}`, { method: "DELETE" });
213
+ if (res.ok) {
214
+ toast.success("Channel deleted");
215
+ fetchChannels();
216
+ }
217
+ } catch {
218
+ toast.error("Failed to delete channel");
219
+ }
220
+ };
221
+
222
+ const handleDirectionToggle = async (ch: ChannelConfig) => {
223
+ const newDirection = ch.direction === "outbound" ? "bidirectional" : "outbound";
224
+ try {
225
+ const res = await fetch(`/api/channels/${ch.id}`, {
226
+ method: "PATCH",
227
+ headers: { "Content-Type": "application/json" },
228
+ body: JSON.stringify({ direction: newDirection }),
229
+ });
230
+ if (res.ok) {
231
+ toast.success(
232
+ newDirection === "bidirectional"
233
+ ? "Bidirectional chat enabled"
234
+ : "Reverted to outbound-only"
235
+ );
236
+ fetchChannels();
237
+ }
238
+ } catch {
239
+ toast.error("Failed to update direction");
240
+ }
241
+ };
242
+
243
+ const getWebhookUrl = (ch: ChannelConfig): string => {
244
+ const base = typeof window !== "undefined" ? window.location.origin : "";
245
+ if (ch.channelType === "telegram") {
246
+ return `${base}/api/channels/inbound/telegram?configId=${ch.id}`;
247
+ }
248
+ if (ch.channelType === "slack") {
249
+ return `${base}/api/channels/inbound/slack?configId=${ch.id}`;
250
+ }
251
+ return `${base}/api/channels/inbound/webhook?configId=${ch.id}`;
252
+ };
253
+
254
+ return (
255
+ <Card>
256
+ <CardHeader>
257
+ <div className="flex items-center justify-between">
258
+ <div>
259
+ <CardTitle className="flex items-center gap-2">
260
+ <Send className="h-5 w-5" />
261
+ Delivery Channels
262
+ </CardTitle>
263
+ <CardDescription>
264
+ Configure Slack, Telegram, or webhook channels for schedule notifications
265
+ and agent output delivery.
266
+ </CardDescription>
267
+ </div>
268
+ <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
269
+ <DialogTrigger asChild>
270
+ <Button size="sm" onClick={resetForm}>
271
+ <Plus className="mr-1 h-4 w-4" />
272
+ Add Channel
273
+ </Button>
274
+ </DialogTrigger>
275
+ <DialogContent>
276
+ <DialogHeader>
277
+ <DialogTitle>Add Delivery Channel</DialogTitle>
278
+ <DialogDescription>
279
+ Configure a new channel for delivering notifications and results.
280
+ </DialogDescription>
281
+ </DialogHeader>
282
+ <div className="space-y-4">
283
+ <div className="space-y-2">
284
+ <Label>Channel Type</Label>
285
+ <Select value={formType} onValueChange={(v) => setFormType(v as typeof formType)}>
286
+ <SelectTrigger>
287
+ <SelectValue />
288
+ </SelectTrigger>
289
+ <SelectContent>
290
+ <SelectItem value="slack">Slack</SelectItem>
291
+ <SelectItem value="telegram">Telegram</SelectItem>
292
+ <SelectItem value="webhook">Webhook</SelectItem>
293
+ </SelectContent>
294
+ </Select>
295
+ </div>
296
+
297
+ <div className="space-y-2">
298
+ <Label>Name</Label>
299
+ <Input
300
+ placeholder="e.g. Team Notifications"
301
+ value={formName}
302
+ onChange={(e) => setFormName(e.target.value)}
303
+ />
304
+ </div>
305
+
306
+ {formType === "slack" && (
307
+ <>
308
+ <div className="space-y-2">
309
+ <Label>Webhook URL</Label>
310
+ <Input
311
+ placeholder="https://hooks.slack.com/services/..."
312
+ value={formWebhookUrl}
313
+ onChange={(e) => setFormWebhookUrl(e.target.value)}
314
+ />
315
+ </div>
316
+ <div className="space-y-2">
317
+ <Label>
318
+ Bot Token{" "}
319
+ <span className="text-muted-foreground font-normal">
320
+ (for bidirectional chat)
321
+ </span>
322
+ </Label>
323
+ <Input
324
+ placeholder="xoxb-..."
325
+ value={formSlackBotToken}
326
+ onChange={(e) => setFormSlackBotToken(e.target.value)}
327
+ />
328
+ </div>
329
+ <div className="space-y-2">
330
+ <Label>
331
+ Signing Secret{" "}
332
+ <span className="text-muted-foreground font-normal">
333
+ (for bidirectional chat)
334
+ </span>
335
+ </Label>
336
+ <Input
337
+ placeholder="From Basic Information → App Credentials"
338
+ value={formSigningSecret}
339
+ onChange={(e) => setFormSigningSecret(e.target.value)}
340
+ />
341
+ </div>
342
+ <div className="space-y-2">
343
+ <Label>
344
+ Channel ID{" "}
345
+ <span className="text-muted-foreground font-normal">
346
+ (for bidirectional chat)
347
+ </span>
348
+ </Label>
349
+ <Input
350
+ placeholder="C0123456789"
351
+ value={formSlackChannelId}
352
+ onChange={(e) => setFormSlackChannelId(e.target.value)}
353
+ />
354
+ </div>
355
+ </>
356
+ )}
357
+
358
+ {formType === "telegram" && (
359
+ <>
360
+ <div className="space-y-2">
361
+ <Label>Bot Token</Label>
362
+ <Input
363
+ placeholder="123456:ABC-DEF..."
364
+ value={formBotToken}
365
+ onChange={(e) => setFormBotToken(e.target.value)}
366
+ />
367
+ </div>
368
+ <div className="space-y-2">
369
+ <Label>Chat ID</Label>
370
+ <Input
371
+ placeholder="-1001234567890"
372
+ value={formChatId}
373
+ onChange={(e) => setFormChatId(e.target.value)}
374
+ />
375
+ </div>
376
+ </>
377
+ )}
378
+
379
+ {formType === "webhook" && (
380
+ <>
381
+ <div className="space-y-2">
382
+ <Label>URL</Label>
383
+ <Input
384
+ placeholder="https://example.com/webhook"
385
+ value={formUrl}
386
+ onChange={(e) => setFormUrl(e.target.value)}
387
+ />
388
+ </div>
389
+ <div className="space-y-2">
390
+ <Label>Custom Headers (JSON, optional)</Label>
391
+ <Input
392
+ placeholder='{"Authorization": "Bearer ..."}'
393
+ value={formHeaders}
394
+ onChange={(e) => setFormHeaders(e.target.value)}
395
+ />
396
+ </div>
397
+ </>
398
+ )}
399
+ </div>
400
+ <DialogFooter>
401
+ <Button variant="outline" onClick={() => setDialogOpen(false)}>
402
+ Cancel
403
+ </Button>
404
+ <Button onClick={handleCreate} disabled={saving || !formName.trim()}>
405
+ {saving ? "Creating..." : "Create Channel"}
406
+ </Button>
407
+ </DialogFooter>
408
+ </DialogContent>
409
+ </Dialog>
410
+ </div>
411
+ </CardHeader>
412
+ <CardContent>
413
+ {channels.length === 0 ? (
414
+ <p className="text-sm text-muted-foreground py-4 text-center">
415
+ No delivery channels configured. Add one to start receiving notifications.
416
+ </p>
417
+ ) : (
418
+ <div className="space-y-3">
419
+ {channels.map((ch) => {
420
+ const Icon = CHANNEL_ICONS[ch.channelType] ?? Globe;
421
+ const TestIcon = TEST_STATUS_ICONS[ch.testStatus] ?? MinusCircle;
422
+ const testColor = TEST_STATUS_COLORS[ch.testStatus] ?? "";
423
+
424
+ return (
425
+ <div
426
+ key={ch.id}
427
+ className="rounded-lg border p-3 space-y-2"
428
+ >
429
+ <div className="flex items-center justify-between">
430
+ <div className="flex items-center gap-3">
431
+ <Icon className="h-4 w-4 text-muted-foreground" />
432
+ <div>
433
+ <div className="flex items-center gap-2">
434
+ <span className="text-sm font-medium">{ch.name}</span>
435
+ <Badge variant="outline" className="text-xs capitalize">
436
+ {ch.channelType}
437
+ </Badge>
438
+ {ch.direction === "bidirectional" && (
439
+ <Badge variant="default" className="text-xs">
440
+ Chat
441
+ </Badge>
442
+ )}
443
+ </div>
444
+ </div>
445
+ </div>
446
+ <div className="flex items-center gap-3">
447
+ {/* Toggles */}
448
+ <div className="flex items-center gap-3">
449
+ {ch.channelType !== "webhook" && (
450
+ <div className="flex items-center gap-1.5">
451
+ <Label
452
+ htmlFor={`chat-${ch.id}`}
453
+ className="text-xs text-muted-foreground cursor-pointer"
454
+ >
455
+ Chat
456
+ </Label>
457
+ <Switch
458
+ id={`chat-${ch.id}`}
459
+ checked={ch.direction === "bidirectional"}
460
+ onCheckedChange={() => handleDirectionToggle(ch)}
461
+ />
462
+ </div>
463
+ )}
464
+ <div className="flex items-center gap-1.5">
465
+ <Label
466
+ htmlFor={`active-${ch.id}`}
467
+ className="text-xs text-muted-foreground cursor-pointer"
468
+ >
469
+ Active
470
+ </Label>
471
+ <Switch
472
+ id={`active-${ch.id}`}
473
+ checked={ch.status === "active"}
474
+ onCheckedChange={() => handleToggle(ch.id, ch.status)}
475
+ />
476
+ </div>
477
+ </div>
478
+ <div className="h-4 w-px bg-border" />
479
+ {/* Actions */}
480
+ <div className="flex items-center gap-1.5">
481
+ <Button
482
+ variant="outline"
483
+ size="sm"
484
+ onClick={() => handleTest(ch.id)}
485
+ disabled={testingId === ch.id}
486
+ >
487
+ {testingId === ch.id ? (
488
+ <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
489
+ ) : (
490
+ <Zap className="mr-1 h-3.5 w-3.5" />
491
+ )}
492
+ Test
493
+ </Button>
494
+ <div className="flex items-center gap-1">
495
+ <TestIcon className={`h-3 w-3 ${testColor}`} />
496
+ <span className={`text-xs ${testColor}`}>
497
+ {ch.testStatus}
498
+ </span>
499
+ </div>
500
+ <Button
501
+ variant="ghost"
502
+ size="icon"
503
+ onClick={() => handleDelete(ch.id)}
504
+ >
505
+ <Trash2 className="h-4 w-4 text-destructive" />
506
+ </Button>
507
+ </div>
508
+ </div>
509
+ </div>
510
+ {ch.direction === "bidirectional" && (
511
+ <div className="pl-7 text-xs text-muted-foreground">
512
+ <span className="font-medium">Webhook URL:</span>{" "}
513
+ <code className="bg-muted px-1 py-0.5 rounded text-[11px] select-all">
514
+ {getWebhookUrl(ch)}
515
+ </code>
516
+ </div>
517
+ )}
518
+ </div>
519
+ );
520
+ })}
521
+ </div>
522
+ )}
523
+ </CardContent>
524
+ </Card>
525
+ );
526
+ }
@@ -17,12 +17,13 @@ import {
17
17
  SelectTrigger,
18
18
  SelectValue,
19
19
  } from "@/components/ui/select";
20
- import { CHAT_MODELS, DEFAULT_CHAT_MODEL } from "@/lib/chat/types";
20
+ import { CHAT_MODELS, DEFAULT_CHAT_MODEL, type ChatModelOption } from "@/lib/chat/types";
21
21
  import { FormSectionCard } from "@/components/shared/form-section-card";
22
22
  import { MessageCircle } from "lucide-react";
23
23
 
24
24
  export function ChatSettingsSection() {
25
25
  const [defaultModel, setDefaultModel] = useState(DEFAULT_CHAT_MODEL);
26
+ const [ollamaModels, setOllamaModels] = useState<ChatModelOption[]>([]);
26
27
 
27
28
  const fetchSettings = useCallback(async () => {
28
29
  try {
@@ -38,6 +39,21 @@ export function ChatSettingsSection() {
38
39
 
39
40
  useEffect(() => {
40
41
  fetchSettings();
42
+ // Fetch Ollama models for the dropdown
43
+ fetch("/api/runtimes/ollama")
44
+ .then((r) => (r.ok ? r.json() : { models: [] }))
45
+ .then((data: { models?: Array<{ name: string }> }) => {
46
+ setOllamaModels(
47
+ (data.models ?? []).map((m) => ({
48
+ id: `ollama:${m.name}`,
49
+ label: m.name.replace(/:latest$/, ""),
50
+ provider: "ollama" as const,
51
+ tier: "Local",
52
+ costLabel: "Free",
53
+ }))
54
+ );
55
+ })
56
+ .catch(() => {});
41
57
  }, [fetchSettings]);
42
58
 
43
59
  const handleModelChange = async (modelId: string) => {
@@ -91,6 +107,16 @@ export function ChatSettingsSection() {
91
107
  ))}
92
108
  </SelectGroup>
93
109
  )}
110
+ {ollamaModels.length > 0 && (
111
+ <SelectGroup>
112
+ <SelectLabel>Ollama (Local)</SelectLabel>
113
+ {ollamaModels.map((m) => (
114
+ <SelectItem key={m.id} value={m.id}>
115
+ {m.label} — {m.tier} ({m.costLabel})
116
+ </SelectItem>
117
+ ))}
118
+ </SelectGroup>
119
+ )}
94
120
  </SelectContent>
95
121
  </Select>
96
122
  </FormSectionCard>
@@ -28,7 +28,7 @@ export function DataManagementSection() {
28
28
  if (data.success) {
29
29
  const d = data.deleted;
30
30
  toast.success(
31
- `Cleared ${d.projects} projects, ${d.tasks} tasks, ${d.workflows} workflows, ${d.schedules} schedules, ${d.documents} documents, ${d.agentLogs} logs, ${d.notifications} notifications, ${d.sampleProfiles} sample profiles, ${d.files} files`
31
+ `Cleared ${d.projects} projects, ${d.tasks} tasks, ${d.workflows} workflows, ${d.schedules} schedules, ${d.documents} documents, ${d.conversations} conversations, ${d.chatMessages} messages, ${d.learnedContext} learned context, ${d.views} views, ${d.agentLogs} logs, ${d.notifications} notifications, ${d.sampleProfiles} sample profiles, ${d.files} files`
32
32
  );
33
33
  } else {
34
34
  toast.error(`Clear failed: ${data.error}`);
@@ -48,7 +48,7 @@ export function DataManagementSection() {
48
48
  if (data.success) {
49
49
  const s = data.seeded;
50
50
  toast.success(
51
- `Seeded ${s.profiles} profiles, ${s.projects} projects, ${s.tasks} tasks, ${s.workflows} workflows, ${s.schedules} schedules, ${s.documents} documents, ${s.agentLogs} logs, ${s.notifications} notifications`
51
+ `Seeded ${s.profiles} profiles, ${s.projects} projects, ${s.tasks} tasks, ${s.workflows} workflows, ${s.schedules} schedules, ${s.documents} documents, ${s.conversations} conversations, ${s.chatMessages} messages, ${s.learnedContext} learned context, ${s.views} views, ${s.agentLogs} logs, ${s.notifications} notifications`
52
52
  );
53
53
  } else {
54
54
  toast.error(`Seed failed: ${data.error}`);
@@ -74,6 +74,7 @@ export function DataManagementSection() {
74
74
  <div className="flex items-center gap-2">
75
75
  <p className="text-sm text-muted-foreground">
76
76
  Delete all projects, tasks, workflows, schedules, documents,
77
+ conversations, chat messages, learned context, saved views,
77
78
  agent logs, notifications, seeded sample profiles, and uploaded
78
79
  files. Authentication settings are preserved.
79
80
  </p>
@@ -99,8 +100,9 @@ export function DataManagementSection() {
99
100
  <p className="text-sm text-muted-foreground">
100
101
  Populate with 3 custom profiles, 5 realistic projects, 25 tasks
101
102
  across varied statuses, 5 workflows, 4 schedules, 12 documents
102
- (XLSX, PDF, DOCX, PPTX), agent logs, and notifications. Existing
103
- data is cleared first.
103
+ (XLSX, PDF, DOCX, PPTX), 3 conversations with chat history,
104
+ learned context, saved views, profile test results, repo imports,
105
+ agent logs, and notifications. Existing data is cleared first.
104
106
  </p>
105
107
  <Button
106
108
  variant="outline"
@@ -122,7 +124,7 @@ export function DataManagementSection() {
122
124
  open={clearOpen}
123
125
  onOpenChange={setClearOpen}
124
126
  title="Clear all data?"
125
- description="This will permanently delete all projects, tasks, workflows, schedules, documents, agent logs, notifications, seeded sample profiles, and uploaded files. Authentication settings will be preserved. This action cannot be undone."
127
+ description="This will permanently delete all projects, tasks, workflows, schedules, documents, conversations, chat messages, learned context, saved views, agent logs, notifications, seeded sample profiles, and uploaded files. Authentication settings will be preserved. This action cannot be undone."
126
128
  confirmLabel="Clear All Data"
127
129
  onConfirm={handleClear}
128
130
  destructive
@@ -132,7 +134,7 @@ export function DataManagementSection() {
132
134
  open={seedOpen}
133
135
  onOpenChange={setSeedOpen}
134
136
  title="Seed sample data?"
135
- description="This will clear all existing data first, then populate with 3 custom profiles, 5 projects, 25 tasks, 5 workflows, 4 schedules, 12 documents (XLSX, PDF, DOCX, PPTX), agent logs, and notifications. Any current data will be lost."
137
+ description="This will clear all existing data first, then populate with 3 custom profiles, 5 projects, 25 tasks, 5 workflows, 4 schedules, 12 documents (XLSX, PDF, DOCX, PPTX), 3 conversations with chat history, learned context, saved views, profile test results, repo imports, agent logs, and notifications. Any current data will be lost."
136
138
  confirmLabel="Seed Data"
137
139
  onConfirm={handleSeed}
138
140
  />