stagent 0.9.5 → 0.10.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 (277) hide show
  1. package/README.md +5 -42
  2. package/dist/cli.js +42 -18
  3. package/docs/.coverage-gaps.json +13 -55
  4. package/docs/.last-generated +1 -1
  5. package/docs/features/provider-runtimes.md +4 -0
  6. package/docs/features/schedules.md +32 -4
  7. package/docs/features/settings.md +28 -5
  8. package/docs/features/tables.md +9 -2
  9. package/docs/features/workflows.md +10 -4
  10. package/docs/journeys/developer.md +15 -1
  11. package/docs/journeys/personal-use.md +21 -4
  12. package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
  13. package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
  14. package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
  15. package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
  16. package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
  17. package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
  18. package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
  19. package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
  20. package/package.json +3 -2
  21. package/src/__tests__/instrumentation-smoke.test.ts +15 -0
  22. package/src/app/analytics/page.tsx +1 -21
  23. package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
  24. package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
  25. package/src/app/api/instance/config/route.ts +41 -0
  26. package/src/app/api/instance/init/route.ts +34 -0
  27. package/src/app/api/instance/upgrade/check/route.ts +26 -0
  28. package/src/app/api/instance/upgrade/route.ts +96 -0
  29. package/src/app/api/instance/upgrade/status/route.ts +35 -0
  30. package/src/app/api/memory/route.ts +0 -11
  31. package/src/app/api/notifications/route.ts +4 -2
  32. package/src/app/api/projects/[id]/route.ts +5 -155
  33. package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
  34. package/src/app/api/schedules/[id]/execute/route.ts +111 -0
  35. package/src/app/api/schedules/[id]/route.ts +9 -1
  36. package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
  37. package/src/app/api/schedules/route.ts +3 -12
  38. package/src/app/api/settings/openai/login/route.ts +22 -0
  39. package/src/app/api/settings/openai/logout/route.ts +7 -0
  40. package/src/app/api/settings/openai/route.ts +21 -1
  41. package/src/app/api/settings/providers/route.ts +35 -8
  42. package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
  43. package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
  44. package/src/app/api/tables/[id]/enrich/route.ts +147 -0
  45. package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
  46. package/src/app/api/tasks/[id]/execute/route.ts +0 -21
  47. package/src/app/api/workflows/[id]/resume/route.ts +59 -0
  48. package/src/app/api/workflows/[id]/status/route.ts +22 -8
  49. package/src/app/api/workspace/context/route.ts +2 -0
  50. package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
  51. package/src/app/chat/page.tsx +11 -0
  52. package/src/app/inbox/page.tsx +12 -5
  53. package/src/app/layout.tsx +42 -21
  54. package/src/app/page.tsx +0 -2
  55. package/src/app/settings/page.tsx +6 -9
  56. package/src/components/chat/__tests__/chat-session-provider.test.tsx +408 -0
  57. package/src/components/chat/chat-command-popover.tsx +2 -2
  58. package/src/components/chat/chat-input.tsx +2 -3
  59. package/src/components/chat/chat-session-provider.tsx +720 -0
  60. package/src/components/chat/chat-shell.tsx +92 -401
  61. package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
  62. package/src/components/instance/instance-section.tsx +382 -0
  63. package/src/components/instance/upgrade-badge.tsx +219 -0
  64. package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
  65. package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
  66. package/src/components/notifications/batch-proposal-review.tsx +20 -5
  67. package/src/components/notifications/inbox-list.tsx +11 -2
  68. package/src/components/notifications/notification-item.tsx +56 -2
  69. package/src/components/notifications/pending-approval-host.tsx +56 -37
  70. package/src/components/schedules/schedule-create-sheet.tsx +19 -1
  71. package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
  72. package/src/components/schedules/schedule-form.tsx +31 -0
  73. package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
  74. package/src/components/settings/auth-method-selector.tsx +19 -4
  75. package/src/components/settings/auth-status-badge.tsx +28 -3
  76. package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
  77. package/src/components/settings/openai-runtime-section.tsx +7 -1
  78. package/src/components/settings/providers-runtimes-section.tsx +138 -19
  79. package/src/components/shared/app-sidebar.tsx +4 -3
  80. package/src/components/shared/command-palette.tsx +4 -5
  81. package/src/components/shared/theme-toggle.tsx +5 -24
  82. package/src/components/shared/workspace-indicator.tsx +61 -2
  83. package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
  84. package/src/components/tables/table-create-sheet.tsx +4 -0
  85. package/src/components/tables/table-enrichment-runs.tsx +103 -0
  86. package/src/components/tables/table-enrichment-sheet.tsx +538 -0
  87. package/src/components/tables/table-spreadsheet.tsx +29 -5
  88. package/src/components/tables/table-toolbar.tsx +10 -1
  89. package/src/components/tasks/kanban-board.tsx +1 -0
  90. package/src/components/tasks/kanban-column.tsx +53 -14
  91. package/src/components/tasks/task-bento-grid.tsx +19 -0
  92. package/src/components/tasks/task-card.tsx +26 -3
  93. package/src/components/tasks/task-chip-bar.tsx +24 -0
  94. package/src/components/tasks/task-result-renderer.tsx +1 -1
  95. package/src/components/workflows/delay-step-body.tsx +109 -0
  96. package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
  97. package/src/components/workflows/loop-status-view.tsx +1 -1
  98. package/src/components/workflows/shared/step-result.tsx +78 -0
  99. package/src/components/workflows/shared/workflow-header.tsx +141 -0
  100. package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
  101. package/src/components/workflows/swarm-dashboard.tsx +2 -15
  102. package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
  103. package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
  104. package/src/components/workflows/workflow-form-view.tsx +133 -16
  105. package/src/components/workflows/workflow-status-view.tsx +30 -740
  106. package/src/instrumentation-node.ts +94 -0
  107. package/src/instrumentation.ts +4 -48
  108. package/src/lib/agents/__tests__/claude-agent.test.ts +199 -0
  109. package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
  110. package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
  111. package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
  112. package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
  113. package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
  114. package/src/lib/agents/claude-agent.ts +155 -18
  115. package/src/lib/agents/execution-manager.ts +0 -35
  116. package/src/lib/agents/learned-context.ts +0 -12
  117. package/src/lib/agents/learning-session.ts +18 -5
  118. package/src/lib/agents/profiles/__tests__/registry.test.ts +6 -4
  119. package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +70 -0
  120. package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +32 -0
  121. package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
  122. package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
  123. package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
  124. package/src/lib/agents/runtime/openai-codex.ts +29 -60
  125. package/src/lib/agents/runtime/types.ts +8 -0
  126. package/src/lib/book/chapter-mapping.ts +11 -0
  127. package/src/lib/book/content.ts +10 -0
  128. package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
  129. package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
  130. package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
  131. package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
  132. package/src/lib/chat/active-streams.ts +27 -0
  133. package/src/lib/chat/codex-engine.ts +16 -17
  134. package/src/lib/chat/context-builder.ts +5 -3
  135. package/src/lib/chat/engine.ts +50 -3
  136. package/src/lib/chat/reconcile.ts +117 -0
  137. package/src/lib/chat/stagent-tools.ts +1 -0
  138. package/src/lib/chat/stream-telemetry.ts +132 -0
  139. package/src/lib/chat/suggested-prompts.ts +28 -1
  140. package/src/lib/chat/system-prompt.ts +26 -1
  141. package/src/lib/chat/tool-catalog.ts +2 -1
  142. package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
  143. package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
  144. package/src/lib/chat/tools/__tests__/task-tools.test.ts +352 -0
  145. package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +217 -0
  146. package/src/lib/chat/tools/document-tools.ts +29 -13
  147. package/src/lib/chat/tools/helpers.ts +39 -0
  148. package/src/lib/chat/tools/notification-tools.ts +9 -5
  149. package/src/lib/chat/tools/project-tools.ts +33 -0
  150. package/src/lib/chat/tools/schedule-tools.ts +44 -11
  151. package/src/lib/chat/tools/table-tools.ts +71 -0
  152. package/src/lib/chat/tools/task-tools.ts +84 -20
  153. package/src/lib/chat/tools/workflow-tools.ts +234 -32
  154. package/src/lib/constants/settings.ts +8 -18
  155. package/src/lib/data/__tests__/clear.test.ts +56 -2
  156. package/src/lib/data/clear.ts +20 -15
  157. package/src/lib/data/delete-project.ts +171 -0
  158. package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
  159. package/src/lib/db/bootstrap.ts +45 -16
  160. package/src/lib/db/index.ts +5 -0
  161. package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
  162. package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
  163. package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
  164. package/src/lib/db/migrations/0026_drop_license.sql +3 -0
  165. package/src/lib/db/migrations/meta/_journal.json +21 -0
  166. package/src/lib/db/schema.ts +68 -23
  167. package/src/lib/environment/workspace-context.ts +13 -1
  168. package/src/lib/import/dedup.ts +4 -54
  169. package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
  170. package/src/lib/instance/__tests__/detect.test.ts +115 -0
  171. package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
  172. package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
  173. package/src/lib/instance/__tests__/settings.test.ts +83 -0
  174. package/src/lib/instance/__tests__/upgrade-poller.test.ts +131 -0
  175. package/src/lib/instance/bootstrap.ts +270 -0
  176. package/src/lib/instance/detect.ts +49 -0
  177. package/src/lib/instance/fingerprint.ts +78 -0
  178. package/src/lib/instance/git-ops.ts +95 -0
  179. package/src/lib/instance/settings.ts +61 -0
  180. package/src/lib/instance/types.ts +77 -0
  181. package/src/lib/instance/upgrade-poller.ts +153 -0
  182. package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
  183. package/src/lib/notifications/visibility.ts +33 -0
  184. package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
  185. package/src/lib/schedules/__tests__/config.test.ts +62 -0
  186. package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
  187. package/src/lib/schedules/__tests__/integration.test.ts +82 -0
  188. package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
  189. package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
  190. package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
  191. package/src/lib/schedules/collision-check.ts +105 -0
  192. package/src/lib/schedules/config.ts +53 -0
  193. package/src/lib/schedules/scheduler.ts +232 -13
  194. package/src/lib/schedules/slot-claim.ts +105 -0
  195. package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
  196. package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
  197. package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
  198. package/src/lib/settings/openai-auth.ts +105 -10
  199. package/src/lib/settings/openai-login-manager.ts +260 -0
  200. package/src/lib/settings/runtime-setup.ts +14 -4
  201. package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
  202. package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
  203. package/src/lib/tables/enrichment-planner.ts +454 -0
  204. package/src/lib/tables/enrichment.ts +328 -0
  205. package/src/lib/tables/query-builder.ts +5 -2
  206. package/src/lib/tables/trigger-evaluator.ts +3 -2
  207. package/src/lib/theme.ts +71 -0
  208. package/src/lib/usage/ledger.ts +2 -18
  209. package/src/lib/util/__tests__/similarity.test.ts +106 -0
  210. package/src/lib/util/similarity.ts +77 -0
  211. package/src/lib/utils/format-timestamp.ts +24 -0
  212. package/src/lib/utils/stagent-paths.ts +12 -0
  213. package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
  214. package/src/lib/validators/__tests__/settings.test.ts +10 -0
  215. package/src/lib/validators/blueprint.ts +70 -9
  216. package/src/lib/validators/profile.ts +2 -2
  217. package/src/lib/validators/settings.ts +3 -1
  218. package/src/lib/workflows/__tests__/delay.test.ts +196 -0
  219. package/src/lib/workflows/__tests__/engine.test.ts +8 -0
  220. package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
  221. package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
  222. package/src/lib/workflows/blueprints/instantiator.ts +22 -1
  223. package/src/lib/workflows/blueprints/types.ts +10 -2
  224. package/src/lib/workflows/delay.ts +106 -0
  225. package/src/lib/workflows/engine.ts +207 -4
  226. package/src/lib/workflows/loop-executor.ts +349 -24
  227. package/src/lib/workflows/post-action.ts +91 -0
  228. package/src/lib/workflows/types.ts +166 -1
  229. package/src/app/api/license/checkout/route.ts +0 -28
  230. package/src/app/api/license/portal/route.ts +0 -26
  231. package/src/app/api/license/route.ts +0 -89
  232. package/src/app/api/license/usage/route.ts +0 -63
  233. package/src/app/api/marketplace/browse/route.ts +0 -15
  234. package/src/app/api/marketplace/import/route.ts +0 -28
  235. package/src/app/api/marketplace/publish/route.ts +0 -40
  236. package/src/app/api/onboarding/email/route.ts +0 -53
  237. package/src/app/api/settings/telemetry/route.ts +0 -14
  238. package/src/app/api/sync/export/route.ts +0 -54
  239. package/src/app/api/sync/restore/route.ts +0 -37
  240. package/src/app/api/sync/sessions/route.ts +0 -24
  241. package/src/app/auth/callback/route.ts +0 -73
  242. package/src/app/marketplace/page.tsx +0 -19
  243. package/src/components/analytics/analytics-gate-card.tsx +0 -101
  244. package/src/components/marketplace/blueprint-card.tsx +0 -61
  245. package/src/components/marketplace/marketplace-browser.tsx +0 -131
  246. package/src/components/onboarding/email-capture-card.tsx +0 -104
  247. package/src/components/settings/activation-form.tsx +0 -95
  248. package/src/components/settings/cloud-account-section.tsx +0 -147
  249. package/src/components/settings/cloud-sync-section.tsx +0 -155
  250. package/src/components/settings/subscription-section.tsx +0 -410
  251. package/src/components/settings/telemetry-section.tsx +0 -80
  252. package/src/components/shared/premium-gate-overlay.tsx +0 -50
  253. package/src/components/shared/schedule-gate-dialog.tsx +0 -64
  254. package/src/components/shared/upgrade-banner.tsx +0 -112
  255. package/src/hooks/use-supabase-auth.ts +0 -79
  256. package/src/lib/billing/email.ts +0 -54
  257. package/src/lib/billing/products.ts +0 -80
  258. package/src/lib/billing/stripe.ts +0 -101
  259. package/src/lib/cloud/supabase-browser.ts +0 -32
  260. package/src/lib/cloud/supabase-client.ts +0 -56
  261. package/src/lib/license/__tests__/features.test.ts +0 -56
  262. package/src/lib/license/__tests__/key-format.test.ts +0 -88
  263. package/src/lib/license/__tests__/manager.test.ts +0 -64
  264. package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
  265. package/src/lib/license/cloud-validation.ts +0 -60
  266. package/src/lib/license/features.ts +0 -44
  267. package/src/lib/license/key-format.ts +0 -101
  268. package/src/lib/license/limit-check.ts +0 -111
  269. package/src/lib/license/limit-queries.ts +0 -51
  270. package/src/lib/license/manager.ts +0 -345
  271. package/src/lib/license/notifications.ts +0 -59
  272. package/src/lib/license/tier-limits.ts +0 -71
  273. package/src/lib/marketplace/marketplace-client.ts +0 -107
  274. package/src/lib/sync/cloud-sync.ts +0 -235
  275. package/src/lib/telemetry/conversion-events.ts +0 -71
  276. package/src/lib/telemetry/queue.ts +0 -122
  277. package/src/lib/validators/license.ts +0 -33
@@ -3,40 +3,21 @@
3
3
  import { useEffect, useState } from "react";
4
4
  import { Moon, Sun } from "lucide-react";
5
5
  import { Button } from "@/components/ui/button";
6
-
7
- type ResolvedTheme = "light" | "dark";
8
-
9
- function resolveThemePreference(): ResolvedTheme {
10
- const stored = localStorage.getItem("stagent-theme");
11
- if (stored === "dark" || stored === "light") return stored;
12
- // Light-first: default to light when no explicit preference stored
13
- return "light";
14
- }
15
-
16
- function applyTheme(theme: ResolvedTheme) {
17
- const root = document.documentElement;
18
- root.classList.toggle("dark", theme === "dark");
19
- root.dataset.theme = theme;
20
- root.style.colorScheme = theme;
21
- root.style.backgroundColor =
22
- theme === "dark" ? "oklch(0.14 0.02 250)" : "oklch(0.985 0.004 250)";
23
- localStorage.setItem("stagent-theme", theme);
24
- document.cookie = `stagent-theme=${theme};path=/;max-age=31536000;SameSite=Lax`;
25
- }
6
+ import { applyTheme, readClientTheme } from "@/lib/theme";
26
7
 
27
8
  export function ThemeToggle() {
28
9
  const [dark, setDark] = useState(false);
29
10
 
30
11
  useEffect(() => {
31
- const theme = resolveThemePreference();
12
+ const theme = readClientTheme();
32
13
  applyTheme(theme);
33
14
  setDark(theme === "dark");
34
15
  }, []);
35
16
 
36
17
  function toggle() {
37
- const next = !dark;
38
- applyTheme(next ? "dark" : "light");
39
- setDark(next);
18
+ const next = dark ? "light" : "dark";
19
+ applyTheme(next);
20
+ setDark(next === "dark");
40
21
  }
41
22
 
42
23
  return (
@@ -1,13 +1,15 @@
1
1
  "use client";
2
2
 
3
3
  import { useEffect, useState } from "react";
4
- import { GitBranch } from "lucide-react";
4
+ import { Database, Folder, GitBranch, Wrench } from "lucide-react";
5
5
 
6
6
  interface WorkspaceData {
7
7
  folderName: string;
8
8
  parentPath: string;
9
9
  gitBranch: string | null;
10
10
  isWorktree: boolean;
11
+ dataDir: string | null;
12
+ dataDirMismatch: boolean;
11
13
  }
12
14
 
13
15
  interface WorkspaceIndicatorProps {
@@ -16,6 +18,8 @@ interface WorkspaceIndicatorProps {
16
18
 
17
19
  export function WorkspaceIndicator({ variant }: WorkspaceIndicatorProps) {
18
20
  const [data, setData] = useState<WorkspaceData | null>(null);
21
+ const [fixState, setFixState] = useState<"idle" | "loading" | "done" | "error">("idle");
22
+ const [fixResult, setFixResult] = useState<string | null>(null);
19
23
 
20
24
  useEffect(() => {
21
25
  fetch("/api/workspace/context")
@@ -26,9 +30,28 @@ export function WorkspaceIndicator({ variant }: WorkspaceIndicatorProps) {
26
30
 
27
31
  if (!data) return null;
28
32
 
33
+ async function handleFix() {
34
+ setFixState("loading");
35
+ try {
36
+ const res = await fetch("/api/workspace/fix-data-dir", { method: "POST" });
37
+ const json = await res.json();
38
+ if (res.ok && json.success) {
39
+ setFixState("done");
40
+ setFixResult(json.dataDir);
41
+ } else {
42
+ setFixState("error");
43
+ setFixResult(json.error || "Fix failed");
44
+ }
45
+ } catch {
46
+ setFixState("error");
47
+ setFixResult("Network error");
48
+ }
49
+ }
50
+
29
51
  if (variant === "inline") {
30
52
  return (
31
53
  <span className="text-sm text-muted-foreground">
54
+ <Folder className="h-3 w-3 inline mr-1" />
32
55
  {data.parentPath}/
33
56
  <span className="font-medium text-foreground">{data.folderName}</span>
34
57
  {data.gitBranch && (
@@ -37,13 +60,20 @@ export function WorkspaceIndicator({ variant }: WorkspaceIndicatorProps) {
37
60
  {data.gitBranch}
38
61
  </span>
39
62
  )}
63
+ {data.dataDir && (
64
+ <span className={`ml-2 inline-flex items-center gap-1 text-xs ${data.dataDirMismatch ? "text-destructive" : "text-muted-foreground/70"}`}>
65
+ <Database className="h-3 w-3" />
66
+ {data.dataDir}
67
+ </span>
68
+ )}
40
69
  </span>
41
70
  );
42
71
  }
43
72
 
44
73
  return (
45
74
  <div className="min-w-0">
46
- <p className="text-[12px] text-muted-foreground truncate">
75
+ <p className="text-[12px] text-muted-foreground truncate flex items-center gap-1">
76
+ <Folder className="h-3 w-3 shrink-0" />
47
77
  {data.parentPath}/<span className="font-semibold text-foreground">{data.folderName}</span>
48
78
  </p>
49
79
  {data.gitBranch && (
@@ -55,6 +85,35 @@ export function WorkspaceIndicator({ variant }: WorkspaceIndicatorProps) {
55
85
  )}
56
86
  </p>
57
87
  )}
88
+ {data.dataDir && (
89
+ <p className={`text-[11px] truncate flex items-center gap-1 mt-0.5 ${data.dataDirMismatch ? "text-destructive" : "text-muted-foreground/70"}`}>
90
+ <Database className="h-3 w-3 shrink-0" />
91
+ {data.dataDir}
92
+ {data.dataDirMismatch && fixState === "idle" && (
93
+ <>
94
+ <span className="text-[10px] bg-destructive/10 text-destructive px-1 rounded">shared</span>
95
+ <button
96
+ onClick={handleFix}
97
+ className="text-[10px] bg-destructive/10 text-destructive px-1.5 py-0.5 rounded hover:bg-destructive/20 transition-colors cursor-pointer inline-flex items-center gap-0.5"
98
+ >
99
+ <Wrench className="h-2.5 w-2.5" />
100
+ Fix
101
+ </button>
102
+ </>
103
+ )}
104
+ {data.dataDirMismatch && fixState === "loading" && (
105
+ <span className="text-[10px] text-muted-foreground animate-pulse">fixing...</span>
106
+ )}
107
+ {data.dataDirMismatch && fixState === "done" && (
108
+ <span className="text-[10px] text-green-600">
109
+ → {fixResult} · restart to apply
110
+ </span>
111
+ )}
112
+ {data.dataDirMismatch && fixState === "error" && (
113
+ <span className="text-[10px] text-destructive">{fixResult}</span>
114
+ )}
115
+ </p>
116
+ )}
58
117
  </div>
59
118
  );
60
119
  }
@@ -0,0 +1,130 @@
1
+ import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
2
+
3
+ import { TableEnrichmentSheet } from "@/components/tables/table-enrichment-sheet";
4
+ import type { EnrichmentPlan } from "@/lib/tables/enrichment-planner";
5
+ import type { ColumnDef } from "@/lib/tables/types";
6
+
7
+ const { pushMock, toastErrorMock, toastSuccessMock } = vi.hoisted(() => ({
8
+ pushMock: vi.fn(),
9
+ toastErrorMock: vi.fn(),
10
+ toastSuccessMock: vi.fn(),
11
+ }));
12
+
13
+ vi.mock("next/navigation", () => ({
14
+ useRouter: () => ({
15
+ push: pushMock,
16
+ }),
17
+ }));
18
+
19
+ vi.mock("sonner", () => ({
20
+ toast: {
21
+ error: toastErrorMock,
22
+ success: toastSuccessMock,
23
+ },
24
+ }));
25
+
26
+ const columns: ColumnDef[] = [
27
+ {
28
+ name: "company",
29
+ displayName: "Company",
30
+ dataType: "text",
31
+ position: 0,
32
+ required: false,
33
+ defaultValue: null,
34
+ config: null,
35
+ },
36
+ ];
37
+
38
+ const previewPlan: EnrichmentPlan = {
39
+ promptMode: "auto",
40
+ strategy: "single-pass-lookup",
41
+ agentProfile: "general",
42
+ reasoning: "Keep the row flow lightweight.",
43
+ steps: [
44
+ {
45
+ id: "lookup",
46
+ name: "Lookup value",
47
+ purpose: "Determine the final typed value for this row",
48
+ prompt: "Return only the final value.",
49
+ agentProfile: "general",
50
+ },
51
+ ],
52
+ targetContract: {
53
+ columnName: "company",
54
+ columnLabel: "Company",
55
+ dataType: "text",
56
+ },
57
+ eligibleRowCount: 2,
58
+ sampleBindings: [{ id: "row-1", company: "Acme" }],
59
+ };
60
+
61
+ describe("TableEnrichmentSheet", () => {
62
+ beforeEach(() => {
63
+ vi.stubGlobal(
64
+ "fetch",
65
+ vi.fn((input: RequestInfo | URL) => {
66
+ const url = String(input);
67
+ if (url === "/api/profiles") {
68
+ return Promise.resolve({
69
+ ok: true,
70
+ json: vi.fn().mockResolvedValue([{ id: "general", name: "General" }]),
71
+ });
72
+ }
73
+ if (url === "/api/tables/table-1/enrich/plan") {
74
+ return Promise.resolve({
75
+ ok: true,
76
+ json: vi.fn().mockResolvedValue(previewPlan),
77
+ });
78
+ }
79
+ if (url === "/api/tables/table-1/enrich") {
80
+ return Promise.resolve({
81
+ ok: true,
82
+ json: vi.fn().mockResolvedValue({ workflowId: "workflow-1", rowCount: 2 }),
83
+ });
84
+ }
85
+ return Promise.reject(new Error(`Unhandled fetch: ${url}`));
86
+ })
87
+ );
88
+ });
89
+
90
+ afterEach(() => {
91
+ vi.unstubAllGlobals();
92
+ vi.clearAllMocks();
93
+ });
94
+
95
+ it("keeps a fresh preview launchable when using the planner recommendation", async () => {
96
+ render(
97
+ <TableEnrichmentSheet
98
+ open
99
+ onOpenChange={() => {}}
100
+ tableId="table-1"
101
+ columns={columns}
102
+ />
103
+ );
104
+
105
+ const dialog = await screen.findByRole("dialog", { name: "Enrich Table" });
106
+ fireEvent.click(within(dialog).getByRole("button", { name: "Preview Plan" }));
107
+
108
+ await screen.findByText("Keep the row flow lightweight.");
109
+
110
+ expect(
111
+ screen.queryByText("Inputs changed after the last preview. Refresh the plan before launching.")
112
+ ).not.toBeInTheDocument();
113
+
114
+ await waitFor(() => {
115
+ expect(within(dialog).getByRole("button", { name: "Launch Enrichment" })).toBeEnabled();
116
+ });
117
+
118
+ fireEvent.click(within(dialog).getByRole("button", { name: "Launch Enrichment" }));
119
+
120
+ await waitFor(() => {
121
+ expect(fetch).toHaveBeenCalledWith(
122
+ "/api/tables/table-1/enrich",
123
+ expect.objectContaining({ method: "POST" })
124
+ );
125
+ expect(pushMock).toHaveBeenCalledWith("/workflows/workflow-1");
126
+ expect(toastErrorMock).not.toHaveBeenCalled();
127
+ expect(toastSuccessMock).toHaveBeenCalled();
128
+ });
129
+ });
130
+ });
@@ -15,6 +15,7 @@ import {
15
15
  import {
16
16
  Sheet,
17
17
  SheetContent,
18
+ SheetDescription,
18
19
  SheetHeader,
19
20
  SheetTitle,
20
21
  SheetFooter,
@@ -132,6 +133,9 @@ export function TableCreateSheet({
132
133
  <SheetContent side="right" className="w-[480px] sm:max-w-[480px]">
133
134
  <SheetHeader>
134
135
  <SheetTitle>Create Table</SheetTitle>
136
+ <SheetDescription>
137
+ Name the table, optionally assign a project, and define the first set of columns.
138
+ </SheetDescription>
135
139
  </SheetHeader>
136
140
 
137
141
  <div className="px-6 pb-6 space-y-4 overflow-y-auto">
@@ -0,0 +1,103 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState, useCallback } from "react";
4
+ import Link from "next/link";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import { Button } from "@/components/ui/button";
7
+ import { workflowStatusVariant } from "@/lib/constants/status-colors";
8
+
9
+ interface EnrichmentRunSummary {
10
+ workflowId: string;
11
+ name: string;
12
+ status: string;
13
+ updatedAt: string;
14
+ targetColumn: string;
15
+ targetColumnLabel: string;
16
+ rowCount: number;
17
+ strategy:
18
+ | "single-pass-lookup"
19
+ | "single-pass-classify"
20
+ | "research-and-synthesize";
21
+ promptMode: "auto" | "custom";
22
+ }
23
+
24
+ interface TableEnrichmentRunsProps {
25
+ tableId: string;
26
+ refreshKey?: number;
27
+ }
28
+
29
+ export function TableEnrichmentRuns({
30
+ tableId,
31
+ refreshKey = 0,
32
+ }: TableEnrichmentRunsProps) {
33
+ const [runs, setRuns] = useState<EnrichmentRunSummary[]>([]);
34
+ const [loading, setLoading] = useState(true);
35
+
36
+ const loadRuns = useCallback(async () => {
37
+ setLoading(true);
38
+ try {
39
+ const res = await fetch(`/api/tables/${tableId}/enrich/runs?limit=5`);
40
+ if (!res.ok) throw new Error("Failed to load runs");
41
+ const data = (await res.json()) as EnrichmentRunSummary[];
42
+ setRuns(data);
43
+ } catch {
44
+ setRuns([]);
45
+ } finally {
46
+ setLoading(false);
47
+ }
48
+ }, [tableId]);
49
+
50
+ useEffect(() => {
51
+ loadRuns();
52
+ }, [loadRuns, refreshKey]);
53
+
54
+ if (!loading && runs.length === 0) {
55
+ return null;
56
+ }
57
+
58
+ return (
59
+ <section className="surface-card-muted rounded-lg border p-4 space-y-3">
60
+ <div className="flex items-center justify-between gap-3">
61
+ <div>
62
+ <h3 className="text-sm font-medium">Recent Enrichments</h3>
63
+ <p className="text-xs text-muted-foreground">
64
+ Recent planner-driven enrichment runs for this table.
65
+ </p>
66
+ </div>
67
+ <Button variant="outline" size="sm" onClick={loadRuns}>
68
+ Refresh
69
+ </Button>
70
+ </div>
71
+
72
+ {loading ? (
73
+ <p className="text-sm text-muted-foreground">Loading recent enrichment runs…</p>
74
+ ) : (
75
+ <div className="space-y-2">
76
+ {runs.map((run) => (
77
+ <div
78
+ key={run.workflowId}
79
+ className="surface-control rounded-lg border px-3 py-2 flex items-center justify-between gap-3"
80
+ >
81
+ <div className="min-w-0">
82
+ <div className="flex items-center gap-2 flex-wrap">
83
+ <p className="text-sm font-medium truncate">{run.targetColumnLabel}</p>
84
+ <Badge variant={workflowStatusVariant[run.status] ?? "outline"}>
85
+ {run.status}
86
+ </Badge>
87
+ <Badge variant="outline">{run.promptMode}</Badge>
88
+ </div>
89
+ <p className="text-xs text-muted-foreground">
90
+ {run.rowCount} rows · {run.strategy} ·{" "}
91
+ {new Date(run.updatedAt).toLocaleString()}
92
+ </p>
93
+ </div>
94
+ <Button asChild variant="ghost" size="sm">
95
+ <Link href={`/workflows/${run.workflowId}`}>View</Link>
96
+ </Button>
97
+ </div>
98
+ ))}
99
+ </div>
100
+ )}
101
+ </section>
102
+ );
103
+ }