stagent 0.5.0 → 0.6.1

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 (256) 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 +104 -0
  38. package/src/app/api/channels/[id]/test/route.ts +52 -0
  39. package/src/app/api/channels/inbound/slack/route.ts +116 -0
  40. package/src/app/api/channels/inbound/telegram/poll/route.ts +140 -0
  41. package/src/app/api/channels/inbound/telegram/route.ts +87 -0
  42. package/src/app/api/channels/route.ts +72 -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/data/clear/route.ts +4 -0
  46. package/src/app/api/data/seed/route.ts +4 -0
  47. package/src/app/api/documents/route.ts +36 -6
  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/runtimes/ollama/route.ts +86 -0
  66. package/src/app/api/runtimes/suggest/route.ts +29 -0
  67. package/src/app/api/schedules/[id]/heartbeat-history/route.ts +77 -0
  68. package/src/app/api/schedules/[id]/route.ts +41 -3
  69. package/src/app/api/schedules/parse/route.ts +66 -0
  70. package/src/app/api/schedules/route.ts +71 -12
  71. package/src/app/api/settings/author-default/route.ts +7 -0
  72. package/src/app/api/settings/learning/route.ts +41 -0
  73. package/src/app/api/settings/ollama/route.ts +34 -0
  74. package/src/app/api/settings/providers/route.ts +57 -0
  75. package/src/app/api/settings/routing/route.ts +24 -0
  76. package/src/app/api/settings/web-search/route.ts +28 -0
  77. package/src/app/api/tasks/[id]/execute/route.ts +13 -1
  78. package/src/app/api/tasks/[id]/respond/route.ts +23 -1
  79. package/src/app/documents/page.tsx +3 -0
  80. package/src/app/environment/page.tsx +8 -1
  81. package/src/app/settings/page.tsx +10 -4
  82. package/src/app/workflows/[id]/edit/page.tsx +2 -0
  83. package/src/app/workflows/new/page.tsx +2 -0
  84. package/src/components/chat/chat-command-popover.tsx +22 -19
  85. package/src/components/chat/chat-input.tsx +5 -0
  86. package/src/components/chat/chat-model-selector.tsx +42 -1
  87. package/src/components/chat/chat-shell.tsx +2 -0
  88. package/src/components/dashboard/welcome-landing.tsx +9 -9
  89. package/src/components/environment/artifact-card.tsx +27 -1
  90. package/src/components/environment/environment-dashboard.tsx +50 -2
  91. package/src/components/environment/environment-summary-card.tsx +5 -2
  92. package/src/components/environment/suggested-profiles.tsx +117 -52
  93. package/src/components/handoffs/handoff-approval-card.tsx +159 -0
  94. package/src/components/memory/memory-browser.tsx +315 -0
  95. package/src/components/profiles/learned-context-panel.tsx +4 -4
  96. package/src/components/profiles/profile-assist-panel.tsx +512 -0
  97. package/src/components/profiles/profile-browser.tsx +109 -8
  98. package/src/components/profiles/profile-card.tsx +29 -1
  99. package/src/components/profiles/profile-detail-view.tsx +200 -28
  100. package/src/components/profiles/profile-form-view.tsx +220 -82
  101. package/src/components/profiles/repo-import-wizard.tsx +648 -0
  102. package/src/components/profiles/smoke-test-editor.tsx +106 -0
  103. package/src/components/schedules/schedule-create-sheet.tsx +9 -1
  104. package/src/components/schedules/schedule-form.tsx +348 -9
  105. package/src/components/schedules/schedule-list.tsx +15 -2
  106. package/src/components/settings/auth-method-selector.tsx +7 -1
  107. package/src/components/settings/budget-guardrails-section.tsx +111 -48
  108. package/src/components/settings/channels-section.tsx +526 -0
  109. package/src/components/settings/chat-settings-section.tsx +27 -1
  110. package/src/components/settings/data-management-section.tsx +8 -6
  111. package/src/components/settings/learning-context-section.tsx +124 -0
  112. package/src/components/settings/ollama-section.tsx +270 -0
  113. package/src/components/settings/providers-runtimes-section.tsx +499 -0
  114. package/src/components/settings/web-search-section.tsx +101 -0
  115. package/src/components/shared/tag-input.tsx +156 -0
  116. package/src/components/tasks/kanban-board.tsx +32 -0
  117. package/src/components/tasks/kanban-column.tsx +4 -2
  118. package/src/components/tasks/task-card.tsx +1 -0
  119. package/src/components/tasks/task-chip-bar.tsx +6 -1
  120. package/src/components/tasks/task-create-panel.tsx +55 -5
  121. package/src/components/workflows/workflow-form-view.tsx +38 -3
  122. package/src/hooks/use-chat-autocomplete.ts +24 -26
  123. package/src/hooks/use-project-skills.ts +66 -0
  124. package/src/hooks/use-tag-suggestions.ts +31 -0
  125. package/src/instrumentation.ts +4 -1
  126. package/src/lib/agents/__tests__/claude-agent.test.ts +3 -0
  127. package/src/lib/agents/__tests__/learned-context.test.ts +10 -0
  128. package/src/lib/agents/agentic-loop.ts +235 -0
  129. package/src/lib/agents/browser-mcp.ts +59 -4
  130. package/src/lib/agents/claude-agent.ts +27 -200
  131. package/src/lib/agents/handoff/bus.ts +164 -0
  132. package/src/lib/agents/handoff/governance.ts +47 -0
  133. package/src/lib/agents/handoff/types.ts +16 -0
  134. package/src/lib/agents/learned-context.ts +27 -7
  135. package/src/lib/agents/memory/decay.ts +61 -0
  136. package/src/lib/agents/memory/extractor.ts +181 -0
  137. package/src/lib/agents/memory/retrieval.ts +96 -0
  138. package/src/lib/agents/memory/types.ts +6 -0
  139. package/src/lib/agents/profiles/__tests__/project-profiles.test.ts +119 -0
  140. package/src/lib/agents/profiles/__tests__/registry.test.ts +11 -3
  141. package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +2 -2
  142. package/src/lib/agents/profiles/builtins/content-creator/SKILL.md +19 -0
  143. package/src/lib/agents/profiles/builtins/content-creator/profile.yaml +27 -0
  144. package/src/lib/agents/profiles/builtins/customer-support-agent/SKILL.md +19 -0
  145. package/src/lib/agents/profiles/builtins/customer-support-agent/profile.yaml +26 -0
  146. package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +2 -2
  147. package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +2 -2
  148. package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +2 -2
  149. package/src/lib/agents/profiles/builtins/financial-analyst/SKILL.md +19 -0
  150. package/src/lib/agents/profiles/builtins/financial-analyst/profile.yaml +24 -0
  151. package/src/lib/agents/profiles/builtins/general/profile.yaml +2 -2
  152. package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +2 -2
  153. package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +2 -2
  154. package/src/lib/agents/profiles/builtins/marketing-strategist/SKILL.md +19 -0
  155. package/src/lib/agents/profiles/builtins/marketing-strategist/profile.yaml +27 -0
  156. package/src/lib/agents/profiles/builtins/operations-coordinator/SKILL.md +19 -0
  157. package/src/lib/agents/profiles/builtins/operations-coordinator/profile.yaml +26 -0
  158. package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +2 -2
  159. package/src/lib/agents/profiles/builtins/researcher/SKILL.md +1 -0
  160. package/src/lib/agents/profiles/builtins/researcher/profile.yaml +2 -2
  161. package/src/lib/agents/profiles/builtins/sales-researcher/SKILL.md +19 -0
  162. package/src/lib/agents/profiles/builtins/sales-researcher/profile.yaml +26 -0
  163. package/src/lib/agents/profiles/builtins/shopping-assistant/SKILL.md +1 -0
  164. package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +2 -2
  165. package/src/lib/agents/profiles/builtins/sweep/profile.yaml +1 -1
  166. package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +2 -2
  167. package/src/lib/agents/profiles/builtins/travel-planner/SKILL.md +2 -0
  168. package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +2 -2
  169. package/src/lib/agents/profiles/builtins/wealth-manager/SKILL.md +2 -0
  170. package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +2 -2
  171. package/src/lib/agents/profiles/project-profiles.ts +193 -0
  172. package/src/lib/agents/profiles/registry.ts +130 -6
  173. package/src/lib/agents/profiles/types.ts +28 -0
  174. package/src/lib/agents/router.ts +174 -2
  175. package/src/lib/agents/runtime/__tests__/catalog.test.ts +15 -4
  176. package/src/lib/agents/runtime/anthropic-direct.ts +644 -0
  177. package/src/lib/agents/runtime/catalog.ts +57 -2
  178. package/src/lib/agents/runtime/claude.ts +205 -1
  179. package/src/lib/agents/runtime/index.ts +22 -0
  180. package/src/lib/agents/runtime/ollama-adapter.ts +409 -0
  181. package/src/lib/agents/runtime/openai-direct.ts +514 -0
  182. package/src/lib/agents/runtime/profile-assist-types.ts +30 -0
  183. package/src/lib/agents/runtime/types.ts +2 -0
  184. package/src/lib/agents/tool-permissions.ts +203 -0
  185. package/src/lib/channels/gateway.ts +321 -0
  186. package/src/lib/channels/poller.ts +268 -0
  187. package/src/lib/channels/registry.ts +90 -0
  188. package/src/lib/channels/slack-adapter.ts +188 -0
  189. package/src/lib/channels/telegram-adapter.ts +218 -0
  190. package/src/lib/channels/types.ts +75 -0
  191. package/src/lib/channels/webhook-adapter.ts +74 -0
  192. package/src/lib/chat/context-builder.ts +22 -2
  193. package/src/lib/chat/engine.ts +95 -13
  194. package/src/lib/chat/ollama-engine.ts +198 -0
  195. package/src/lib/chat/stagent-tools.ts +106 -20
  196. package/src/lib/chat/tool-catalog.ts +24 -0
  197. package/src/lib/chat/tool-registry.ts +90 -0
  198. package/src/lib/chat/tools/chat-history-tools.ts +4 -4
  199. package/src/lib/chat/tools/document-tools.ts +7 -7
  200. package/src/lib/chat/tools/handoff-tools.ts +70 -0
  201. package/src/lib/chat/tools/notification-tools.ts +4 -4
  202. package/src/lib/chat/tools/profile-tools.ts +3 -3
  203. package/src/lib/chat/tools/project-tools.ts +3 -3
  204. package/src/lib/chat/tools/schedule-tools.ts +29 -13
  205. package/src/lib/chat/tools/settings-tools.ts +2 -2
  206. package/src/lib/chat/tools/task-tools.ts +66 -11
  207. package/src/lib/chat/tools/usage-tools.ts +2 -2
  208. package/src/lib/chat/tools/workflow-tools.ts +8 -8
  209. package/src/lib/chat/types.ts +11 -5
  210. package/src/lib/constants/known-tools.ts +19 -0
  211. package/src/lib/constants/prose-styles.ts +1 -1
  212. package/src/lib/constants/settings.ts +7 -0
  213. package/src/lib/data/channel-bindings.ts +85 -0
  214. package/src/lib/data/clear.ts +22 -0
  215. package/src/lib/data/profile-test-results.ts +48 -0
  216. package/src/lib/data/seed-data/conversations.ts +196 -0
  217. package/src/lib/data/seed-data/learned-context.ts +99 -0
  218. package/src/lib/data/seed-data/notifications.ts +54 -1
  219. package/src/lib/data/seed-data/profile-test-results.ts +96 -0
  220. package/src/lib/data/seed-data/repo-imports.ts +51 -0
  221. package/src/lib/data/seed-data/views.ts +60 -0
  222. package/src/lib/data/seed.ts +51 -0
  223. package/src/lib/db/bootstrap.ts +162 -0
  224. package/src/lib/db/migrations/0013_add_repo_imports.sql +15 -0
  225. package/src/lib/db/migrations/0014_add_linked_profile_id.sql +3 -0
  226. package/src/lib/db/migrations/0015_add_channel_bindings.sql +23 -0
  227. package/src/lib/db/schema.ts +190 -1
  228. package/src/lib/environment/__tests__/auto-scan.test.ts +86 -0
  229. package/src/lib/environment/__tests__/profile-linker.test.ts +187 -0
  230. package/src/lib/environment/auto-scan.ts +48 -0
  231. package/src/lib/environment/data.ts +25 -0
  232. package/src/lib/environment/profile-generator.ts +40 -10
  233. package/src/lib/environment/profile-linker.ts +143 -0
  234. package/src/lib/environment/profile-rules.ts +96 -0
  235. package/src/lib/import/dedup.ts +149 -0
  236. package/src/lib/import/format-adapter.ts +631 -0
  237. package/src/lib/import/github-api.ts +219 -0
  238. package/src/lib/import/repo-scanner.ts +251 -0
  239. package/src/lib/schedules/__tests__/nlp-parser.test.ts +330 -0
  240. package/src/lib/schedules/active-hours.ts +120 -0
  241. package/src/lib/schedules/heartbeat-parser.ts +224 -0
  242. package/src/lib/schedules/heartbeat-prompt.ts +153 -0
  243. package/src/lib/schedules/nlp-parser.ts +357 -0
  244. package/src/lib/schedules/scheduler.ts +218 -3
  245. package/src/lib/settings/__tests__/budget-guardrails.test.ts +39 -1
  246. package/src/lib/settings/helpers.ts +6 -0
  247. package/src/lib/settings/routing.ts +24 -0
  248. package/src/lib/settings/runtime-setup.ts +28 -1
  249. package/src/lib/usage/ledger.ts +2 -1
  250. package/src/lib/validators/__tests__/settings.test.ts +9 -0
  251. package/src/lib/validators/profile.ts +39 -0
  252. package/src/lib/workflows/blueprints/builtins/business-daily-briefing.yaml +102 -0
  253. package/src/lib/workflows/blueprints/builtins/content-marketing-pipeline.yaml +90 -0
  254. package/src/lib/workflows/blueprints/builtins/customer-support-triage.yaml +107 -0
  255. package/src/lib/workflows/blueprints/builtins/financial-reporting.yaml +104 -0
  256. package/src/lib/workflows/blueprints/builtins/lead-research-pipeline.yaml +82 -0
@@ -0,0 +1,159 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import {
5
+ ArrowRight,
6
+ CheckCircle2,
7
+ XCircle,
8
+ Clock,
9
+ AlertTriangle,
10
+ } from "lucide-react";
11
+ import { toast } from "sonner";
12
+ import { Button } from "@/components/ui/button";
13
+ import { Badge } from "@/components/ui/badge";
14
+
15
+ interface HandoffApprovalCardProps {
16
+ id: string;
17
+ fromProfileId: string;
18
+ toProfileId: string;
19
+ subject: string;
20
+ body: string;
21
+ priority: number;
22
+ chainDepth: number;
23
+ status: string;
24
+ requiresApproval: boolean;
25
+ onActionComplete?: () => void;
26
+ }
27
+
28
+ const PRIORITY_LABELS: Record<number, string> = {
29
+ 0: "Critical",
30
+ 1: "High",
31
+ 2: "Medium",
32
+ 3: "Low",
33
+ };
34
+
35
+ const PRIORITY_VARIANTS: Record<number, "destructive" | "default" | "secondary" | "outline"> = {
36
+ 0: "destructive",
37
+ 1: "default",
38
+ 2: "secondary",
39
+ 3: "outline",
40
+ };
41
+
42
+ const STATUS_ICONS: Record<string, typeof Clock> = {
43
+ pending: Clock,
44
+ accepted: CheckCircle2,
45
+ in_progress: Clock,
46
+ completed: CheckCircle2,
47
+ rejected: XCircle,
48
+ expired: AlertTriangle,
49
+ };
50
+
51
+ const STATUS_VARIANTS: Record<string, "default" | "secondary" | "destructive" | "outline"> = {
52
+ pending: "outline",
53
+ accepted: "default",
54
+ in_progress: "default",
55
+ completed: "secondary",
56
+ rejected: "destructive",
57
+ expired: "outline",
58
+ };
59
+
60
+ export function HandoffApprovalCard({
61
+ id,
62
+ fromProfileId,
63
+ toProfileId,
64
+ subject,
65
+ body,
66
+ priority,
67
+ chainDepth,
68
+ status,
69
+ requiresApproval,
70
+ onActionComplete,
71
+ }: HandoffApprovalCardProps) {
72
+ const [acting, setActing] = useState(false);
73
+
74
+ const StatusIcon = STATUS_ICONS[status] ?? Clock;
75
+
76
+ const handleAction = async (action: "approve" | "reject") => {
77
+ setActing(true);
78
+ try {
79
+ const res = await fetch(`/api/handoffs/${id}`, {
80
+ method: "PATCH",
81
+ headers: { "Content-Type": "application/json" },
82
+ body: JSON.stringify({ action, approvedBy: "user" }),
83
+ });
84
+
85
+ if (res.ok) {
86
+ toast.success(`Handoff ${action === "approve" ? "approved" : "rejected"}`);
87
+ onActionComplete?.();
88
+ } else {
89
+ const data = await res.json();
90
+ toast.error(data.error ?? `Failed to ${action} handoff`);
91
+ }
92
+ } catch {
93
+ toast.error(`Failed to ${action} handoff`);
94
+ } finally {
95
+ setActing(false);
96
+ }
97
+ };
98
+
99
+ return (
100
+ <div className="rounded-lg border p-4 space-y-3">
101
+ <div className="flex items-start justify-between">
102
+ <div className="space-y-1">
103
+ <h4 className="text-sm font-medium">{subject}</h4>
104
+ <div className="flex items-center gap-2 text-xs text-muted-foreground">
105
+ <span className="font-mono">{fromProfileId}</span>
106
+ <ArrowRight className="h-3 w-3" />
107
+ <span className="font-mono">{toProfileId}</span>
108
+ </div>
109
+ </div>
110
+ <div className="flex items-center gap-2">
111
+ <Badge variant={PRIORITY_VARIANTS[priority] ?? "secondary"}>
112
+ {PRIORITY_LABELS[priority] ?? "Medium"}
113
+ </Badge>
114
+ <Badge variant={STATUS_VARIANTS[status] ?? "outline"}>
115
+ <StatusIcon className="mr-1 h-3 w-3" />
116
+ {status}
117
+ </Badge>
118
+ </div>
119
+ </div>
120
+
121
+ <p className="text-sm text-muted-foreground line-clamp-3">{body}</p>
122
+
123
+ <div className="flex items-center justify-between">
124
+ <div className="flex items-center gap-2 text-xs text-muted-foreground">
125
+ {chainDepth > 0 && (
126
+ <span>Chain depth: {chainDepth}</span>
127
+ )}
128
+ {requiresApproval && status === "pending" && (
129
+ <Badge variant="outline" className="text-xs">
130
+ Awaiting approval
131
+ </Badge>
132
+ )}
133
+ </div>
134
+
135
+ {status === "pending" && requiresApproval && (
136
+ <div className="flex items-center gap-2">
137
+ <Button
138
+ variant="outline"
139
+ size="sm"
140
+ onClick={() => handleAction("reject")}
141
+ disabled={acting}
142
+ >
143
+ <XCircle className="mr-1 h-3.5 w-3.5" />
144
+ Reject
145
+ </Button>
146
+ <Button
147
+ size="sm"
148
+ onClick={() => handleAction("approve")}
149
+ disabled={acting}
150
+ >
151
+ <CheckCircle2 className="mr-1 h-3.5 w-3.5" />
152
+ Approve
153
+ </Button>
154
+ </div>
155
+ )}
156
+ </div>
157
+ </div>
158
+ );
159
+ }
@@ -0,0 +1,315 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Input } from "@/components/ui/input";
7
+ import {
8
+ Select,
9
+ SelectContent,
10
+ SelectItem,
11
+ SelectTrigger,
12
+ SelectValue,
13
+ } from "@/components/ui/select";
14
+ import {
15
+ Table,
16
+ TableBody,
17
+ TableCell,
18
+ TableHead,
19
+ TableHeader,
20
+ TableRow,
21
+ } from "@/components/ui/table";
22
+ import { EmptyState } from "@/components/shared/empty-state";
23
+ import { Brain, Archive, XCircle, Check, Pencil } from "lucide-react";
24
+ import { toast } from "sonner";
25
+ import type { AgentMemoryRow } from "@/lib/db/schema";
26
+
27
+ const CATEGORY_VARIANTS: Record<string, "default" | "secondary" | "outline" | "destructive"> = {
28
+ fact: "default",
29
+ preference: "secondary",
30
+ pattern: "outline",
31
+ outcome: "destructive",
32
+ };
33
+
34
+ const STATUS_VARIANTS: Record<string, "default" | "secondary" | "outline" | "destructive"> = {
35
+ active: "default",
36
+ decayed: "secondary",
37
+ archived: "outline",
38
+ rejected: "destructive",
39
+ };
40
+
41
+ interface MemoryBrowserProps {
42
+ profileId: string;
43
+ }
44
+
45
+ export function MemoryBrowser({ profileId }: MemoryBrowserProps) {
46
+ const [memories, setMemories] = useState<AgentMemoryRow[]>([]);
47
+ const [loading, setLoading] = useState(true);
48
+ const [categoryFilter, setCategoryFilter] = useState<string>("all");
49
+ const [statusFilter, setStatusFilter] = useState<string>("active");
50
+ const [editingId, setEditingId] = useState<string | null>(null);
51
+ const [editConfidence, setEditConfidence] = useState<string>("");
52
+
53
+ const fetchMemories = useCallback(async () => {
54
+ setLoading(true);
55
+ try {
56
+ const params = new URLSearchParams({ profileId });
57
+ if (categoryFilter !== "all") params.set("category", categoryFilter);
58
+ if (statusFilter !== "all") params.set("status", statusFilter);
59
+
60
+ const res = await fetch(`/api/memory?${params}`);
61
+ if (res.ok) {
62
+ setMemories(await res.json());
63
+ }
64
+ } catch {
65
+ toast.error("Failed to load memories");
66
+ } finally {
67
+ setLoading(false);
68
+ }
69
+ }, [profileId, categoryFilter, statusFilter]);
70
+
71
+ useEffect(() => {
72
+ fetchMemories();
73
+ }, [fetchMemories]);
74
+
75
+ async function handleArchive(id: string) {
76
+ try {
77
+ const res = await fetch("/api/memory", {
78
+ method: "DELETE",
79
+ headers: { "Content-Type": "application/json" },
80
+ body: JSON.stringify({ id }),
81
+ });
82
+ if (res.ok) {
83
+ toast.success("Memory archived");
84
+ fetchMemories();
85
+ }
86
+ } catch {
87
+ toast.error("Failed to archive memory");
88
+ }
89
+ }
90
+
91
+ async function handleReject(id: string) {
92
+ try {
93
+ const res = await fetch("/api/memory", {
94
+ method: "PATCH",
95
+ headers: { "Content-Type": "application/json" },
96
+ body: JSON.stringify({ id, status: "rejected" }),
97
+ });
98
+ if (res.ok) {
99
+ toast.success("Memory rejected");
100
+ fetchMemories();
101
+ }
102
+ } catch {
103
+ toast.error("Failed to reject memory");
104
+ }
105
+ }
106
+
107
+ async function handleSaveConfidence(id: string) {
108
+ const value = parseInt(editConfidence, 10);
109
+ if (isNaN(value) || value < 0 || value > 1000) {
110
+ toast.error("Confidence must be between 0 and 1000");
111
+ return;
112
+ }
113
+ try {
114
+ const res = await fetch("/api/memory", {
115
+ method: "PATCH",
116
+ headers: { "Content-Type": "application/json" },
117
+ body: JSON.stringify({ id, confidence: value }),
118
+ });
119
+ if (res.ok) {
120
+ toast.success("Confidence updated");
121
+ setEditingId(null);
122
+ fetchMemories();
123
+ }
124
+ } catch {
125
+ toast.error("Failed to update confidence");
126
+ }
127
+ }
128
+
129
+ function formatDate(dateValue: string | Date | null | undefined): string {
130
+ if (!dateValue) return "Never";
131
+ const d = typeof dateValue === "string" ? new Date(dateValue) : dateValue;
132
+ return d.toLocaleDateString();
133
+ }
134
+
135
+ if (!loading && memories.length === 0) {
136
+ return (
137
+ <div className="space-y-4">
138
+ <div className="flex items-center gap-2">
139
+ <Select value={statusFilter} onValueChange={setStatusFilter}>
140
+ <SelectTrigger className="w-[140px]">
141
+ <SelectValue placeholder="Status" />
142
+ </SelectTrigger>
143
+ <SelectContent>
144
+ <SelectItem value="all">All Status</SelectItem>
145
+ <SelectItem value="active">Active</SelectItem>
146
+ <SelectItem value="decayed">Decayed</SelectItem>
147
+ <SelectItem value="archived">Archived</SelectItem>
148
+ <SelectItem value="rejected">Rejected</SelectItem>
149
+ </SelectContent>
150
+ </Select>
151
+ </div>
152
+ <EmptyState
153
+ icon={Brain}
154
+ heading="No memories yet"
155
+ description="Episodic memories are extracted from task results and stored as factual knowledge for this profile."
156
+ />
157
+ </div>
158
+ );
159
+ }
160
+
161
+ return (
162
+ <div className="space-y-4">
163
+ {/* Filters */}
164
+ <div className="flex items-center gap-2">
165
+ <Select value={categoryFilter} onValueChange={setCategoryFilter}>
166
+ <SelectTrigger className="w-[140px]">
167
+ <SelectValue placeholder="Category" />
168
+ </SelectTrigger>
169
+ <SelectContent>
170
+ <SelectItem value="all">All Categories</SelectItem>
171
+ <SelectItem value="fact">Fact</SelectItem>
172
+ <SelectItem value="preference">Preference</SelectItem>
173
+ <SelectItem value="pattern">Pattern</SelectItem>
174
+ <SelectItem value="outcome">Outcome</SelectItem>
175
+ </SelectContent>
176
+ </Select>
177
+ <Select value={statusFilter} onValueChange={setStatusFilter}>
178
+ <SelectTrigger className="w-[140px]">
179
+ <SelectValue placeholder="Status" />
180
+ </SelectTrigger>
181
+ <SelectContent>
182
+ <SelectItem value="all">All Status</SelectItem>
183
+ <SelectItem value="active">Active</SelectItem>
184
+ <SelectItem value="decayed">Decayed</SelectItem>
185
+ <SelectItem value="archived">Archived</SelectItem>
186
+ <SelectItem value="rejected">Rejected</SelectItem>
187
+ </SelectContent>
188
+ </Select>
189
+ <span className="text-sm text-muted-foreground ml-auto">
190
+ {memories.length} {memories.length === 1 ? "memory" : "memories"}
191
+ </span>
192
+ </div>
193
+
194
+ {/* Table */}
195
+ <div className="rounded-lg border">
196
+ <Table>
197
+ <TableHeader>
198
+ <TableRow>
199
+ <TableHead className="w-[40%]">Content</TableHead>
200
+ <TableHead>Category</TableHead>
201
+ <TableHead>Confidence</TableHead>
202
+ <TableHead className="text-right">Accesses</TableHead>
203
+ <TableHead>Last Accessed</TableHead>
204
+ <TableHead>Status</TableHead>
205
+ <TableHead className="text-right">Actions</TableHead>
206
+ </TableRow>
207
+ </TableHeader>
208
+ <TableBody>
209
+ {loading ? (
210
+ <TableRow>
211
+ <TableCell colSpan={7} className="text-center py-8 text-muted-foreground">
212
+ Loading...
213
+ </TableCell>
214
+ </TableRow>
215
+ ) : (
216
+ memories.map((memory) => (
217
+ <TableRow key={memory.id}>
218
+ <TableCell className="max-w-[300px]">
219
+ <p className="text-sm truncate" title={memory.content}>
220
+ {memory.content}
221
+ </p>
222
+ </TableCell>
223
+ <TableCell>
224
+ <Badge variant={CATEGORY_VARIANTS[memory.category] ?? "outline"}>
225
+ {memory.category}
226
+ </Badge>
227
+ </TableCell>
228
+ <TableCell>
229
+ {editingId === memory.id ? (
230
+ <div className="flex items-center gap-1">
231
+ <Input
232
+ type="number"
233
+ min={0}
234
+ max={1000}
235
+ value={editConfidence}
236
+ onChange={(e) => setEditConfidence(e.target.value)}
237
+ className="w-20 h-7 text-xs"
238
+ />
239
+ <Button
240
+ size="icon"
241
+ variant="ghost"
242
+ className="h-6 w-6"
243
+ onClick={() => handleSaveConfidence(memory.id)}
244
+ >
245
+ <Check className="h-3 w-3" />
246
+ </Button>
247
+ </div>
248
+ ) : (
249
+ <div className="flex items-center gap-2">
250
+ <div className="w-16 h-1.5 rounded-full bg-muted overflow-hidden">
251
+ <div
252
+ className="h-full bg-primary rounded-full"
253
+ style={{ width: `${(memory.confidence / 1000) * 100}%` }}
254
+ />
255
+ </div>
256
+ <span className="text-xs text-muted-foreground">
257
+ {Math.round((memory.confidence / 1000) * 100)}%
258
+ </span>
259
+ </div>
260
+ )}
261
+ </TableCell>
262
+ <TableCell className="text-right text-sm">
263
+ {memory.accessCount}
264
+ </TableCell>
265
+ <TableCell className="text-sm text-muted-foreground">
266
+ {formatDate(memory.lastAccessedAt)}
267
+ </TableCell>
268
+ <TableCell>
269
+ <Badge variant={STATUS_VARIANTS[memory.status] ?? "outline"}>
270
+ {memory.status}
271
+ </Badge>
272
+ </TableCell>
273
+ <TableCell className="text-right">
274
+ <div className="flex items-center justify-end gap-1">
275
+ <Button
276
+ size="icon"
277
+ variant="ghost"
278
+ className="h-7 w-7"
279
+ title="Edit confidence"
280
+ onClick={() => {
281
+ setEditingId(memory.id);
282
+ setEditConfidence(String(memory.confidence));
283
+ }}
284
+ >
285
+ <Pencil className="h-3 w-3" />
286
+ </Button>
287
+ <Button
288
+ size="icon"
289
+ variant="ghost"
290
+ className="h-7 w-7"
291
+ title="Archive"
292
+ onClick={() => handleArchive(memory.id)}
293
+ >
294
+ <Archive className="h-3 w-3" />
295
+ </Button>
296
+ <Button
297
+ size="icon"
298
+ variant="ghost"
299
+ className="h-7 w-7 text-destructive"
300
+ title="Reject"
301
+ onClick={() => handleReject(memory.id)}
302
+ >
303
+ <XCircle className="h-3 w-3" />
304
+ </Button>
305
+ </div>
306
+ </TableCell>
307
+ </TableRow>
308
+ ))
309
+ )}
310
+ </TableBody>
311
+ </Table>
312
+ </div>
313
+ </div>
314
+ );
315
+ }
@@ -236,7 +236,7 @@ export function LearnedContextPanel({ profileId }: LearnedContextPanelProps) {
236
236
  <History className="h-3 w-3" />
237
237
  Version History
238
238
  </div>
239
- <div className="max-h-72 space-y-2 overflow-y-auto">
239
+ <div className="max-h-[32rem] space-y-2 overflow-y-auto">
240
240
  {entries.map((entry) => {
241
241
  const { row, snapshotContent, derivedDiff } = entry;
242
242
  const badgeConfig = CHANGE_TYPE_BADGE[row.changeType] ?? {
@@ -327,13 +327,13 @@ export function LearnedContextPanel({ profileId }: LearnedContextPanelProps) {
327
327
  </div>
328
328
 
329
329
  {snapshotContent ? (
330
- <div className={`${PROSE_NOTIFICATION} max-h-28 overflow-auto rounded-md bg-background/50 p-2`}>
330
+ <div className={`${PROSE_NOTIFICATION} max-h-48 overflow-auto rounded-md bg-background/50 p-2`}>
331
331
  <ReactMarkdown remarkPlugins={[remarkGfm]}>
332
332
  {snapshotContent}
333
333
  </ReactMarkdown>
334
334
  </div>
335
335
  ) : row.diff ? (
336
- <div className={`${PROSE_NOTIFICATION} max-h-24 overflow-auto rounded-md bg-background/50 p-2`}>
336
+ <div className={`${PROSE_NOTIFICATION} max-h-40 overflow-auto rounded-md bg-background/50 p-2`}>
337
337
  <ReactMarkdown remarkPlugins={[remarkGfm]}>
338
338
  {row.diff}
339
339
  </ReactMarkdown>
@@ -353,7 +353,7 @@ export function LearnedContextPanel({ profileId }: LearnedContextPanelProps) {
353
353
  : `vs v${derivedDiff.previousVersion}`}
354
354
  </span>
355
355
  </div>
356
- <div className="max-h-44 space-y-1 overflow-y-auto font-mono text-xs">
356
+ <div className="max-h-64 space-y-1 overflow-y-auto font-mono text-xs">
357
357
  {derivedDiff.lines.map((line, index) => {
358
358
  const toneClass =
359
359
  line.kind === "added"