stagent 0.1.12 → 0.1.13

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 (77) hide show
  1. package/README.md +44 -50
  2. package/package.json +1 -1
  3. package/public/readme/cost-usage-list.png +0 -0
  4. package/public/readme/dashboard-bulk-select.png +0 -0
  5. package/public/readme/dashboard-card-edit.png +0 -0
  6. package/public/readme/dashboard-create-form-ai-applied.png +0 -0
  7. package/public/readme/dashboard-create-form-ai-assist.png +0 -0
  8. package/public/readme/dashboard-create-form-empty.png +0 -0
  9. package/public/readme/dashboard-create-form-filled.png +0 -0
  10. package/public/readme/dashboard-filtered.png +0 -0
  11. package/public/readme/dashboard-list.png +0 -0
  12. package/public/readme/dashboard-workflow-confirm.png +0 -0
  13. package/public/readme/home-below-fold.png +0 -0
  14. package/public/readme/home-list.png +0 -0
  15. package/public/readme/inbox-list.png +0 -0
  16. package/public/readme/playbook-list.png +0 -0
  17. package/public/readme/profiles-list.png +0 -0
  18. package/public/readme/settings-list.png +0 -0
  19. package/public/readme/workflows-list.png +0 -0
  20. package/src/app/api/tasks/[id]/route.ts +54 -3
  21. package/src/app/api/workflows/[id]/route.ts +43 -4
  22. package/src/app/api/workflows/[id]/status/route.ts +70 -2
  23. package/src/app/api/workflows/from-assist/route.ts +6 -32
  24. package/src/app/dashboard/page.tsx +59 -21
  25. package/src/app/documents/[id]/page.tsx +10 -8
  26. package/src/app/globals.css +11 -0
  27. package/src/app/page.tsx +60 -3
  28. package/src/app/tasks/[id]/page.tsx +22 -2
  29. package/src/components/costs/cost-dashboard.tsx +1 -1
  30. package/src/components/dashboard/greeting.tsx +3 -1
  31. package/src/components/dashboard/priority-queue.tsx +58 -9
  32. package/src/components/dashboard/stats-cards.tsx +16 -2
  33. package/src/components/documents/document-chip-bar.tsx +183 -0
  34. package/src/components/documents/document-content-renderer.tsx +146 -0
  35. package/src/components/documents/document-detail-view.tsx +16 -239
  36. package/src/components/documents/image-zoom-view.tsx +60 -0
  37. package/src/components/documents/smart-extracted-text.tsx +47 -0
  38. package/src/components/documents/utils.ts +70 -0
  39. package/src/components/notifications/inbox-list.tsx +4 -5
  40. package/src/components/notifications/notification-item.tsx +72 -8
  41. package/src/components/notifications/pending-approval-host.tsx +7 -4
  42. package/src/components/playbook/playbook-detail-view.tsx +6 -4
  43. package/src/components/profiles/profile-browser.tsx +1 -0
  44. package/src/components/profiles/profile-card.tsx +16 -8
  45. package/src/components/profiles/profile-detail-view.tsx +6 -1
  46. package/src/components/shared/app-sidebar.tsx +2 -2
  47. package/src/components/tasks/__tests__/kanban-board-accessibility.test.tsx +1 -1
  48. package/src/components/tasks/ai-assist-panel.tsx +108 -78
  49. package/src/components/tasks/content-preview.tsx +2 -1
  50. package/src/components/tasks/kanban-board.tsx +57 -5
  51. package/src/components/tasks/kanban-column.tsx +34 -23
  52. package/src/components/tasks/task-bento-cell.tsx +50 -0
  53. package/src/components/tasks/task-bento-grid.tsx +155 -0
  54. package/src/components/tasks/task-card.tsx +14 -16
  55. package/src/components/tasks/task-chip-bar.tsx +207 -0
  56. package/src/components/tasks/task-detail-view.tsx +42 -190
  57. package/src/components/tasks/task-result-renderer.tsx +33 -0
  58. package/src/components/workflows/blueprint-gallery.tsx +19 -12
  59. package/src/components/workflows/blueprint-preview.tsx +8 -1
  60. package/src/components/workflows/loop-status-view.tsx +2 -8
  61. package/src/components/workflows/swarm-dashboard.tsx +2 -3
  62. package/src/components/workflows/workflow-confirmation-view.tsx +2 -7
  63. package/src/components/workflows/workflow-full-output.tsx +80 -0
  64. package/src/components/workflows/workflow-kanban-card.tsx +121 -0
  65. package/src/components/workflows/workflow-list.tsx +47 -42
  66. package/src/components/workflows/workflow-status-view.tsx +160 -20
  67. package/src/lib/agents/learning-session.ts +138 -18
  68. package/src/lib/constants/card-icons.tsx +202 -0
  69. package/src/lib/constants/prose-styles.ts +7 -0
  70. package/src/lib/constants/task-status.ts +3 -0
  71. package/src/lib/docs/reader.ts +8 -3
  72. package/src/lib/documents/context-builder.ts +41 -0
  73. package/src/lib/queries/chart-data.ts +20 -1
  74. package/src/lib/workflows/engine.ts +57 -61
  75. package/src/lib/workflows/types.ts +2 -0
  76. package/tsconfig.json +2 -1
  77. package/src/components/documents/document-preview.tsx +0 -68
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Stagent
2
2
 
3
- > Govern Your AI Agents. Operate With Oversight.
3
+ > Governed AI Agent Workspace Supervised Local Execution, Workflows, Documents, and Provider Runtimes.
4
4
 
5
- [![Next.js 16](https://img.shields.io/badge/Next.js-16-black)](https://nextjs.org/) [![React 19](https://img.shields.io/badge/React-19-61DAFB)](https://react.dev/) [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178C6)](https://www.typescriptlang.org/) [![Claude Agent SDK](https://img.shields.io/badge/Claude-Agent_SDK-D97706)](https://docs.anthropic.com/) [![OpenAI Codex App Server](https://img.shields.io/badge/OpenAI-Codex_App_Server-10A37F)](https://developers.openai.com/codex/app-server) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
5
+ [![npm](https://img.shields.io/npm/v/stagent)](https://www.npmjs.com/package/stagent) [![Next.js 16](https://img.shields.io/badge/Next.js-16-black)](https://nextjs.org/) [![React 19](https://img.shields.io/badge/React-19-61DAFB)](https://react.dev/) [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178C6)](https://www.typescriptlang.org/) [![Claude Agent SDK](https://img.shields.io/badge/Claude-Agent_SDK-D97706)](https://docs.anthropic.com/) [![OpenAI Codex App Server](https://img.shields.io/badge/OpenAI-Codex_App_Server-10A37F)](https://developers.openai.com/codex/app-server) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
6
+
7
+ **[stagent.io](https://stagent.io)** · **[GitHub](https://github.com/navam-io/stagent)**
6
8
 
7
9
  ## Quick Start
8
10
 
@@ -10,7 +12,7 @@
10
12
  npx stagent
11
13
  ```
12
14
 
13
- Open [localhost:3000](http://localhost:3000).
15
+ Open [localhost:3000](http://localhost:3000). That's it — zero config, local SQLite, own your data.
14
16
 
15
17
  **Profiles & Policies** · **Blueprints & Schedules** · **Built-in Playbook** · **Open Source**
16
18
 
@@ -24,13 +26,20 @@ Open [localhost:3000](http://localhost:3000).
24
26
 
25
27
  ## Why Stagent
26
28
 
27
- AI agents are powerful — but production use breaks down when teams cannot see what the agent is doing, which rules it follows, or intervene before an unsafe action lands. Stagent gives you a governed operations workspace where every run is visible, every profile is reusable, and every approval is auditable. Run it locally with `npx stagent` and own your data from day one.
29
+ AI agents are powerful — but production use breaks down when teams cannot see what the agent is doing, which rules it follows, or intervene before an unsafe action lands. Stagent gives you a **governed operations workspace** where every run is visible, every profile is reusable, and every approval is auditable.
30
+
31
+ - **Local-first** — SQLite database, no cloud dependency, `npx stagent` and go
32
+ - **Multi-provider** — Claude Code + OpenAI Codex App Server behind one runtime registry
33
+ - **Human-in-the-loop** — Inbox approvals, ambient toasts, tool permission policies
34
+ - **Reusable profiles** — 13+ agent profiles with instructions, tool policies, and runtime tuning
35
+ - **Workflow orchestration** — 6 patterns (sequence, planner-executor, checkpoint, parallel, loop, swarm)
36
+ - **Cost governance** — Usage metering, budgets, and spend visibility per provider and model
28
37
 
29
38
  ---
30
39
 
31
40
  ## Runtime Bridge
32
41
 
33
- Stagent ships a shared runtime registry that routes tasks, schedules, and workflow steps through two governed execution backends: **Claude Code** (Anthropic Claude Agent SDK) and **OpenAI Codex App Server**. Both land in the same inbox, monitoring, and task-state surfaces — so switching providers is a config change, not a rewrite.
42
+ Stagent ships a shared runtime registry that routes tasks, schedules, and workflow steps through two governed execution backends: **Claude Code** (Anthropic Claude Agent SDK) and **OpenAI Codex App Server**. Both land in the same inbox, monitoring, and task-state surfaces — switching providers is a config change, not a rewrite.
34
43
 
35
44
  ---
36
45
 
@@ -120,9 +129,9 @@ Claude Agent SDK integration with the `canUseTool` polling pattern remains the d
120
129
  OpenAI Codex App Server is integrated as Stagent's second governed runtime. Codex-backed tasks preserve project working directories, document context, resumable thread IDs, inbox approval requests, user questions, and provider-labeled logs. The same runtime can also power task assist, scheduled firings, and workflow child tasks.
121
130
 
122
131
  #### Agent Profiles
123
- Profile-backed execution with specialist definitions for different job types. Each profile packages instructions, allowed tools, max turns, and output format so teams can reuse behavior intentionally instead of relying on ad hoc prompts. Workflow steps and schedules can reference profiles directly, and runtimes can be selected independently when provider support differs.
132
+ Profile-backed execution with specialist definitions for different job types. Each profile packages instructions, allowed tools, max turns, and output format so teams can reuse behavior intentionally instead of relying on ad hoc prompts. Profile cards display role-based icon circles with keyword-inferred colors (blue for work, purple for personal), alongside domain tags, runtime badges, and tool counts. Workflow steps and schedules can reference profiles directly, and runtimes can be selected independently when provider support differs.
124
133
 
125
- <img src="https://raw.githubusercontent.com/navam-io/stagent/main/public/readme/profiles-list.png" alt="Stagent agent profiles" width="1200" />
134
+ <img src="https://raw.githubusercontent.com/navam-io/stagent/main/public/readme/profiles-list.png" alt="Stagent agent profiles with role-based icon circles" width="1200" />
126
135
 
127
136
  #### Workflows
128
137
  Multi-step task orchestration with six patterns:
@@ -167,12 +176,12 @@ Resume failed or cancelled agent tasks with one click. Tracks retry counts (limi
167
176
  Iterative agent loop pattern with four stop conditions: max iterations, time budget, human cancel, and agent-signaled completion. Each iteration creates a child task with previous output as context. Loop status view with iteration timeline, progress bar, and expandable results. Pause/resume via DB status polling.
168
177
 
169
178
  #### Agent Profile Catalog
170
- Curated agent profiles across work and personal domains, built as portable Claude Code skill directories with `profile.yaml` sidecars. The profile gallery supports domain filtering and search, while YAML customization, GitHub import, and behavioral smoke tests keep profile behavior inspectable and reusable.
179
+ Curated agent profiles across work and personal domains, built as portable Claude Code skill directories with `profile.yaml` sidecars. The profile gallery displays role-based icon circles with keyword-inferred colors and supports domain filtering and search, while YAML customization, GitHub import, and behavioral smoke tests keep profile behavior inspectable and reusable.
171
180
 
172
181
  #### Workflow Blueprints
173
- Pre-configured workflow templates across work and personal domains. Browse blueprints in a gallery with filtering and search, preview steps and required variables, fill in a dynamic form, and create draft workflows with resolved prompts and profile assignments. Create custom blueprints via YAML or import from GitHub URLs. Lineage tracking connects workflows back to their source blueprint.
182
+ Pre-configured workflow templates across work and personal domains. Browse blueprints in a gallery with pattern-colored icon circles, domain tags, and difficulty badges. Preview steps and required variables, fill in a dynamic form, and create draft workflows with resolved prompts and profile assignments. Create custom blueprints via YAML or import from GitHub URLs. Lineage tracking connects workflows back to their source blueprint.
174
183
 
175
- <img src="https://raw.githubusercontent.com/navam-io/stagent/main/public/readme/workflows-list.png" alt="Stagent workflow management" width="1200" />
184
+ <img src="https://raw.githubusercontent.com/navam-io/stagent/main/public/readme/workflows-list.png" alt="Stagent workflows with keyword-inferred icon circles" width="1200" />
176
185
 
177
186
  ### Documents
178
187
 
@@ -234,7 +243,9 @@ Real-time agent log streaming via Server-Sent Events. Filter by task or event ty
234
243
  File upload with drag-and-drop in task creation. Type-aware content preview for text, markdown (via react-markdown), code, and JSON. Copy-to-clipboard and download-as-file for task outputs.
235
244
 
236
245
  #### Settings
237
- Configuration hub with provider-aware sections: Claude authentication (API key or OAuth), OpenAI Codex runtime API-key management, tool permissions (saved "Always Allow" patterns with revoke), and data management.
246
+ Configuration hub with provider-aware sections: Claude authentication (API key or OAuth), OpenAI Codex runtime API-key management, tool permissions (saved "Always Allow" patterns with revoke), permission presets, budget configuration, and data management.
247
+
248
+ <img src="https://raw.githubusercontent.com/navam-io/stagent/main/public/readme/settings-list.png" alt="Stagent settings" width="1200" />
238
249
 
239
250
  #### CLI
240
251
  The `npx stagent` entry point boots a Next.js server from the published npm package. It is built from `bin/cli.ts` into `dist/cli.js` using tsup, and serves as the primary distribution channel — no clone required.
@@ -395,44 +406,23 @@ All 14 features shipped across three layers:
395
406
  | **Core** | Project management, task board, agent integration, inbox notifications, monitoring dashboard |
396
407
  | **Polish** | Homepage dashboard, UX fixes, workflow engine, AI task assist, content handling, session management |
397
408
 
398
- ### Post-MVP — Complete (31 features)
399
-
400
- | Category | Feature | What shipped |
401
- |----------|---------|-------------|
402
- | **Documents** | File Attachments | Upload data layer with project/task linking |
403
- | | Document Preprocessing | Text extraction for 5 formats (text, PDF, images, Office, spreadsheets) |
404
- | | Agent Document Context | Automatic document injection into agent prompts |
405
- | | Document Browser | Table/grid views, search, filters, bulk operations at `/documents` |
406
- | | Document Output Generation | Agent-generated documents as deliverables |
407
- | **Agent Intelligence** | Multi-Agent Routing | Profile registry (4 profiles), task classifier, per-step profile assignment |
408
- | | Autonomous Loop Execution | 4 stop conditions, iteration context chaining, pause/resume, loop status view |
409
- | | Multi-Agent Swarm | Mayor → worker pool → refinery orchestration with retryable stages |
410
- | | AI Assist → Workflows | Bridge task assist into workflow engine with profile assignment and pattern selection |
411
- | | Agent Self-Improvement | Pattern extraction from logs, human-approved context evolution, versioned rollback |
412
- | | Workflow Context Batching | Workflow-scoped proposal buffering with batch approve/reject |
413
- | **Agent Profiles** | Agent Profile Catalog | 13 domain-specific profiles, GitHub import, behavioral testing, MCP passthrough |
414
- | | Workflow Blueprints | 8 templates, gallery, YAML editor, dynamic forms, GitHub import, lineage tracking |
415
- | **UI Enhancement** | Ambient Approvals | Shell-level approval presenter on any route for fast supervision |
416
- | | Micro-Visualizations | Sparklines, mini bars, donut rings — zero-dependency SVG charts |
417
- | | Command Palette | ⌘K palette with navigation, create actions, recent items, theme toggle |
418
- | | Operational Surface | Cross-route composition with consistent layout, density, and interaction patterns |
419
- | | Profile Surface | Profile gallery stability, detail views, and behavioral testing UI |
420
- | | Accessibility | ARIA labels, keyboard navigation, focus management, screen reader support |
421
- | | UI Density Refinement | Tightened spacing, typography, and visual hierarchy across all routes |
422
- | | Kanban Board Operations | Inline editing, bulk operations, card animations, edit dialog |
423
- | | Board Context Persistence | Persisted filters, sort order, and project selection across sessions |
424
- | **Platform** | Scheduled Prompt Loops | Cron + human-friendly intervals, one-shot/recurring, pause/resume lifecycle |
425
- | | Tool Permission Persistence | "Always Allow" patterns, pre-check bypass, Settings management |
426
- | | Tool Permission Presets | 3 layered presets (read-only, git-safe, full-auto) with risk badges |
427
- | | Provider Runtimes | Shared runtime registry with Claude Code and OpenAI Codex App Server adapters |
428
- | | OpenAI Codex Runtime | Codex App Server integration with inbox approvals, logs, and thread resumption |
429
- | | Cross-Provider Profiles | Profile compatibility layer ensuring profiles work across Claude and Codex runtimes |
430
- | | Parallel Fork/Join | 2-5 concurrent research branches with synthesis step |
431
- | **Runtime Quality** | E2E Test Automation | API-level test suite covering both runtimes, 4 profiles, 4 workflow patterns |
432
- | **Knowledge** | Playbook | Built-in documentation with usage-stage awareness, adoption heatmap, guided learning journeys |
433
- | **Governance** | Usage Metering Ledger | Provider-normalized token and spend tracking across all execution paths |
434
- | | Spend Budget Guardrails | Per-project and global budgets with enforcement and alerts |
435
- | | Cost & Usage Dashboard | Summary cards, trend views, provider/model breakdowns, budget audit visibility |
409
+ ### Post-MVP — 31 features shipped
410
+
411
+ | Category | Features |
412
+ |----------|---------|
413
+ | **Documents** (5) | File attachments, preprocessing (5 formats), agent context injection, document browser, output generation |
414
+ | **Agent Intelligence** (6) | Multi-agent routing, autonomous loops, multi-agent swarm, AI assist→workflows, agent self-improvement, workflow context batching |
415
+ | **Agent Profiles** (2) | Agent profile catalog (13+ profiles), workflow blueprints (8 templates) |
416
+ | **UI Enhancement** (13) | Ambient approvals, learned context UX, micro-visualizations, command palette, operational surface, profile surface, accessibility, UI density, kanban operations, board persistence, detail view redesign, playbook documentation, workflow UX overhaul (in-progress) |
417
+ | **Platform** (8) | Scheduled prompt loops, tool permissions, provider runtimes, OpenAI Codex runtime, cross-provider profiles, parallel fork/join, tool permission presets, npm publish (deferred) |
418
+ | **Runtime Quality** (2) | SDK runtime hardening, E2E test automation |
419
+ | **Governance** (3) | Usage metering ledger, spend budget guardrails, cost & usage dashboard |
420
+
421
+ ### In Progress
422
+
423
+ | Feature | Description |
424
+ |---------|-------------|
425
+ | Workflow UX Overhaul | Document context propagation, output readability, dashboard visibility, AI assist guidance |
436
426
 
437
427
  ---
438
428
 
@@ -441,7 +431,7 @@ All 14 features shipped across three layers:
441
431
  ### Contributor Setup
442
432
 
443
433
  ```bash
444
- git clone <repo-url> && cd stagent && npm install
434
+ git clone https://github.com/navam-io/stagent.git && cd stagent && npm install
445
435
 
446
436
  # Set up one or both runtime credentials
447
437
  cat > .env.local <<'EOF'
@@ -463,6 +453,10 @@ npm run dev
463
453
 
464
454
  See `AGENTS.md` for architecture details and development conventions.
465
455
 
456
+ ---
457
+
466
458
  ## License
467
459
 
468
460
  Licensed under the [Apache License 2.0](LICENSE).
461
+
462
+ Copyright 2025-2026 [Navam](https://navam.io)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stagent",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Governed AI agent workspace for supervised local execution, workflows, documents, and provider runtimes.",
5
5
  "keywords": [
6
6
  "ai",
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,7 +1,7 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  import { db } from "@/lib/db";
3
- import { tasks } from "@/lib/db/schema";
4
- import { eq } from "drizzle-orm";
3
+ import { tasks, projects, workflows, schedules, usageLedger } from "@/lib/db/schema";
4
+ import { eq, sum, min, max } from "drizzle-orm";
5
5
  import { updateTaskSchema } from "@/lib/validators/task";
6
6
  import { isValidTransition, type TaskStatus } from "@/lib/constants/task-status";
7
7
  import { validateRuntimeProfileAssignment } from "@/lib/agents/profiles/assignment-validation";
@@ -13,7 +13,58 @@ export async function GET(
13
13
  const { id } = await params;
14
14
  const [task] = await db.select().from(tasks).where(eq(tasks.id, id));
15
15
  if (!task) return NextResponse.json({ error: "Not found" }, { status: 404 });
16
- return NextResponse.json(task);
16
+
17
+ // Join relationship names
18
+ let projectName: string | undefined;
19
+ let workflowName: string | undefined;
20
+ let scheduleName: string | undefined;
21
+
22
+ if (task.projectId) {
23
+ const [p] = await db.select({ name: projects.name }).from(projects).where(eq(projects.id, task.projectId));
24
+ projectName = p?.name;
25
+ }
26
+ if (task.workflowId) {
27
+ const [w] = await db.select({ name: workflows.name }).from(workflows).where(eq(workflows.id, task.workflowId));
28
+ workflowName = w?.name;
29
+ }
30
+ if (task.scheduleId) {
31
+ const [s] = await db.select({ name: schedules.name }).from(schedules).where(eq(schedules.id, task.scheduleId));
32
+ scheduleName = s?.name;
33
+ }
34
+
35
+ // Aggregate usage from usage_ledger
36
+ const [usage] = await db
37
+ .select({
38
+ inputTokens: sum(usageLedger.inputTokens),
39
+ outputTokens: sum(usageLedger.outputTokens),
40
+ totalTokens: sum(usageLedger.totalTokens),
41
+ costMicros: sum(usageLedger.costMicros),
42
+ modelId: max(usageLedger.modelId),
43
+ startedAt: min(usageLedger.startedAt),
44
+ finishedAt: max(usageLedger.finishedAt),
45
+ })
46
+ .from(usageLedger)
47
+ .where(eq(usageLedger.taskId, id));
48
+
49
+ const hasUsage = usage?.totalTokens != null;
50
+
51
+ return NextResponse.json({
52
+ ...task,
53
+ projectName,
54
+ workflowName,
55
+ scheduleName,
56
+ usage: hasUsage
57
+ ? {
58
+ inputTokens: usage.inputTokens ? Number(usage.inputTokens) : null,
59
+ outputTokens: usage.outputTokens ? Number(usage.outputTokens) : null,
60
+ totalTokens: usage.totalTokens ? Number(usage.totalTokens) : null,
61
+ costMicros: usage.costMicros ? Number(usage.costMicros) : null,
62
+ modelId: usage.modelId ?? null,
63
+ startedAt: usage.startedAt instanceof Date ? usage.startedAt.toISOString() : usage.startedAt,
64
+ finishedAt: usage.finishedAt instanceof Date ? usage.finishedAt.toISOString() : usage.finishedAt,
65
+ }
66
+ : undefined,
67
+ });
17
68
  }
18
69
 
19
70
  export async function PATCH(
@@ -1,7 +1,15 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  import { db } from "@/lib/db";
3
- import { workflows } from "@/lib/db/schema";
4
- import { eq } from "drizzle-orm";
3
+ import {
4
+ workflows,
5
+ tasks,
6
+ agentLogs,
7
+ notifications,
8
+ documents,
9
+ learnedContext,
10
+ usageLedger,
11
+ } from "@/lib/db/schema";
12
+ import { eq, inArray } from "drizzle-orm";
5
13
  import type { WorkflowDefinition } from "@/lib/workflows/types";
6
14
  import { validateWorkflowDefinitionAssignments } from "@/lib/agents/profiles/assignment-validation";
7
15
  import { validateWorkflowDefinition } from "@/lib/workflows/definition-validation";
@@ -127,7 +135,38 @@ export async function DELETE(
127
135
  );
128
136
  }
129
137
 
130
- await db.delete(workflows).where(eq(workflows.id, id));
138
+ try {
139
+ // Cascade-delete in FK-safe order
140
+ const taskIds = db
141
+ .select({ id: tasks.id })
142
+ .from(tasks)
143
+ .where(eq(tasks.workflowId, id))
144
+ .all()
145
+ .map((r) => r.id);
146
+
147
+ // Delete usage ledger entries (reference both workflowId and taskId)
148
+ db.delete(usageLedger).where(eq(usageLedger.workflowId, id)).run();
149
+
150
+ if (taskIds.length > 0) {
151
+ // Delete task children first
152
+ db.delete(agentLogs).where(inArray(agentLogs.taskId, taskIds)).run();
153
+ db.delete(notifications).where(inArray(notifications.taskId, taskIds)).run();
154
+ db.delete(documents).where(inArray(documents.taskId, taskIds)).run();
155
+ db.delete(learnedContext).where(inArray(learnedContext.sourceTaskId, taskIds)).run();
156
+ db.delete(usageLedger).where(inArray(usageLedger.taskId, taskIds)).run();
157
+ // Delete the tasks themselves
158
+ db.delete(tasks).where(inArray(tasks.id, taskIds)).run();
159
+ }
160
+
161
+ // Finally delete the workflow
162
+ db.delete(workflows).where(eq(workflows.id, id)).run();
131
163
 
132
- return NextResponse.json({ deleted: true });
164
+ return NextResponse.json({ deleted: true });
165
+ } catch (err) {
166
+ console.error("Workflow delete failed:", err);
167
+ return NextResponse.json(
168
+ { error: err instanceof Error ? err.message : "Delete failed" },
169
+ { status: 500 }
170
+ );
171
+ }
133
172
  }
@@ -1,9 +1,71 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  import { db } from "@/lib/db";
3
- import { workflows } from "@/lib/db/schema";
4
- import { eq } from "drizzle-orm";
3
+ import { workflows, documents } from "@/lib/db/schema";
4
+ import { eq, and, inArray } from "drizzle-orm";
5
5
  import { parseWorkflowState } from "@/lib/workflows/engine";
6
6
 
7
+ /** Collect output documents for workflow step tasks + input documents from parent task */
8
+ async function getWorkflowDocuments(
9
+ state: { stepStates: Array<{ taskId?: string }> } | null,
10
+ sourceTaskId?: string
11
+ ) {
12
+ const stepDocuments: Record<string, Array<{ id: string; originalName: string; mimeType: string; storagePath: string; direction: string }>> = {};
13
+ const parentDocuments: Array<{ id: string; originalName: string; mimeType: string; storagePath: string; direction: string }> = [];
14
+
15
+ try {
16
+ // Collect step task IDs that have completed
17
+ const stepTaskIds = (state?.stepStates ?? [])
18
+ .map((s) => s.taskId)
19
+ .filter((id): id is string => !!id);
20
+
21
+ if (stepTaskIds.length > 0) {
22
+ const outputDocs = await db
23
+ .select({
24
+ id: documents.id,
25
+ taskId: documents.taskId,
26
+ originalName: documents.originalName,
27
+ mimeType: documents.mimeType,
28
+ storagePath: documents.storagePath,
29
+ direction: documents.direction,
30
+ })
31
+ .from(documents)
32
+ .where(and(inArray(documents.taskId, stepTaskIds), eq(documents.direction, "output")));
33
+
34
+ for (const doc of outputDocs) {
35
+ if (!doc.taskId) continue;
36
+ if (!stepDocuments[doc.taskId]) stepDocuments[doc.taskId] = [];
37
+ stepDocuments[doc.taskId].push({
38
+ id: doc.id,
39
+ originalName: doc.originalName,
40
+ mimeType: doc.mimeType,
41
+ storagePath: doc.storagePath,
42
+ direction: doc.direction,
43
+ });
44
+ }
45
+ }
46
+
47
+ // Parent task input documents
48
+ if (sourceTaskId) {
49
+ const inputDocs = await db
50
+ .select({
51
+ id: documents.id,
52
+ originalName: documents.originalName,
53
+ mimeType: documents.mimeType,
54
+ storagePath: documents.storagePath,
55
+ direction: documents.direction,
56
+ })
57
+ .from(documents)
58
+ .where(and(eq(documents.taskId, sourceTaskId), eq(documents.direction, "input")));
59
+
60
+ parentDocuments.push(...inputDocs);
61
+ }
62
+ } catch (error) {
63
+ console.error("[workflow-status] Failed to query workflow documents:", error);
64
+ }
65
+
66
+ return { stepDocuments, parentDocuments };
67
+ }
68
+
7
69
  export async function GET(
8
70
  _req: NextRequest,
9
71
  { params }: { params: Promise<{ id: string }> }
@@ -20,6 +82,8 @@ export async function GET(
20
82
  }
21
83
 
22
84
  const { definition, state, loopState } = parseWorkflowState(workflow.definition);
85
+ const sourceTaskId: string | undefined = definition.sourceTaskId;
86
+ const { stepDocuments, parentDocuments } = await getWorkflowDocuments(state, sourceTaskId);
23
87
 
24
88
  // Loop pattern returns loop-specific data instead of step states
25
89
  if (definition.pattern === "loop") {
@@ -34,6 +98,8 @@ export async function GET(
34
98
  swarmConfig: definition.swarmConfig,
35
99
  loopState,
36
100
  steps: definition.steps,
101
+ stepDocuments,
102
+ parentDocuments,
37
103
  });
38
104
  }
39
105
 
@@ -50,5 +116,7 @@ export async function GET(
50
116
  state: state?.stepStates[i] ?? { stepId: step.id, status: "pending" },
51
117
  })),
52
118
  workflowState: state,
119
+ stepDocuments,
120
+ parentDocuments,
53
121
  });
54
122
  }
@@ -23,7 +23,7 @@ interface FromAssistBody {
23
23
 
24
24
  export async function POST(req: NextRequest) {
25
25
  const body = (await req.json()) as FromAssistBody;
26
- const { name, projectId, definition, priority, assignedAgent, executeImmediately, parentTask } = body;
26
+ const { name, projectId, definition, priority, assignedAgent, executeImmediately } = body;
27
27
 
28
28
  if (!name?.trim()) {
29
29
  return NextResponse.json({ error: "Name is required" }, { status: 400 });
@@ -46,53 +46,27 @@ export async function POST(req: NextRequest) {
46
46
  return NextResponse.json({ error: compatibilityError }, { status: 400 });
47
47
  }
48
48
 
49
- // Transaction: create workflow + tasks + optional parent task atomically
49
+ // Transaction: create workflow + step tasks atomically (no phantom parent task)
50
50
  const workflowId = crypto.randomUUID();
51
51
  const now = new Date();
52
52
  const taskIds: string[] = [];
53
- let parentTaskId: string | null = null;
54
53
 
55
54
  try {
56
55
  db.transaction((tx) => {
57
- // Create parent task (no workflowId visible on dashboard)
58
- if (parentTask?.title) {
59
- parentTaskId = crypto.randomUUID();
60
- tx.insert(tasks)
61
- .values({
62
- id: parentTaskId,
63
- title: parentTask.title,
64
- description: parentTask.description || null,
65
- projectId: projectId || null,
66
- workflowId: null,
67
- status: executeImmediately ? "running" : "planned",
68
- assignedAgent: assignedAgent ?? null,
69
- agentProfile: parentTask.agentProfile ?? null,
70
- priority: priority ?? 2,
71
- createdAt: now,
72
- updatedAt: now,
73
- })
74
- .run();
75
- }
76
-
77
- // Store sourceTaskId in definition for parent↔workflow linkage
78
- const defToStore = parentTaskId
79
- ? { ...definition, sourceTaskId: parentTaskId }
80
- : definition;
81
-
82
- // Create workflow
56
+ // Create workflow no sourceTaskId needed since there's no parent task
83
57
  tx.insert(workflows)
84
58
  .values({
85
59
  id: workflowId,
86
60
  name: name.trim(),
87
61
  projectId: projectId || null,
88
- definition: JSON.stringify(defToStore),
62
+ definition: JSON.stringify(definition),
89
63
  status: executeImmediately ? "active" : "draft",
90
64
  createdAt: now,
91
65
  updatedAt: now,
92
66
  })
93
67
  .run();
94
68
 
95
- // Create tasks for each step (with workflowId — hidden from dashboard)
69
+ // Create tasks for each step (with workflowId — hidden from dashboard kanban)
96
70
  for (const step of definition.steps) {
97
71
  const taskId = crypto.randomUUID();
98
72
  taskIds.push(taskId);
@@ -135,7 +109,7 @@ export async function POST(req: NextRequest) {
135
109
  {
136
110
  workflow: created,
137
111
  taskIds,
138
- parentTaskId,
112
+ parentTaskId: null,
139
113
  status: executeImmediately ? "started" : "created",
140
114
  },
141
115
  { status: 201 }
@@ -2,9 +2,11 @@ import { Suspense } from "react";
2
2
  import { db } from "@/lib/db";
3
3
  import { tasks, projects, workflows } from "@/lib/db/schema";
4
4
  import { desc, isNull } from "drizzle-orm";
5
+ import { parseWorkflowState } from "@/lib/workflows/engine";
5
6
  import { KanbanBoard } from "@/components/tasks/kanban-board";
6
7
  import { SkeletonBoard } from "@/components/tasks/skeleton-board";
7
8
  import type { TaskItem } from "@/components/tasks/task-card";
9
+ import type { WorkflowKanbanItem } from "@/components/workflows/workflow-kanban-card";
8
10
 
9
11
  export const dynamic = "force-dynamic";
10
12
 
@@ -21,38 +23,74 @@ async function BoardContent() {
21
23
  .from(projects)
22
24
  .orderBy(projects.name);
23
25
 
24
- // Build project name lookup for task cards
26
+ // Build project name lookup
25
27
  const projectMap = new Map(allProjects.map((p) => [p.id, p.name]));
26
28
 
27
- // Look up linked workflows for parent tasks (via sourceTaskId in definition JSON)
29
+ // Fetch all workflows for kanban display
28
30
  const allWorkflows = await db
29
- .select({ id: workflows.id, definition: workflows.definition, status: workflows.status })
30
- .from(workflows);
31
-
32
- const linkedWorkflowMap = new Map<string, { workflowId: string; workflowStatus: string }>();
33
- for (const w of allWorkflows) {
34
- try {
35
- const def = JSON.parse(w.definition);
36
- if (def.sourceTaskId) {
37
- linkedWorkflowMap.set(def.sourceTaskId, {
38
- workflowId: w.id,
39
- workflowStatus: w.status,
40
- });
41
- }
42
- } catch { /* skip invalid JSON */ }
43
- }
31
+ .select()
32
+ .from(workflows)
33
+ .orderBy(desc(workflows.updatedAt));
44
34
 
45
- // Serialize Date objects for client component consumption
35
+ // Serialize tasks (no more linkedWorkflow fields)
46
36
  const serializedTasks: TaskItem[] = allTasks.map((t) => ({
47
37
  ...t,
48
38
  projectName: t.projectId ? projectMap.get(t.projectId) ?? undefined : undefined,
49
- linkedWorkflowId: linkedWorkflowMap.get(t.id)?.workflowId,
50
- linkedWorkflowStatus: linkedWorkflowMap.get(t.id)?.workflowStatus,
51
39
  createdAt: t.createdAt.toISOString(),
52
40
  updatedAt: t.updatedAt.toISOString(),
53
41
  }));
54
42
 
55
- return <KanbanBoard initialTasks={serializedTasks} projects={allProjects} />;
43
+ // Build workflow kanban items with step progress
44
+ const serializedWorkflows: WorkflowKanbanItem[] = allWorkflows.map((w) => {
45
+ let stepProgress = { current: 0, total: 0 };
46
+ let currentStepName: string | undefined;
47
+
48
+ try {
49
+ const { definition, state } = parseWorkflowState(w.definition);
50
+ if (definition.steps) {
51
+ stepProgress.total = definition.steps.length;
52
+ if (state) {
53
+ stepProgress.current = state.stepStates.filter(
54
+ (s) => s.status === "completed"
55
+ ).length;
56
+ const running = state.stepStates.find((s) => s.status === "running");
57
+ if (running) {
58
+ currentStepName = definition.steps.find(
59
+ (step) => step.id === running.stepId
60
+ )?.name;
61
+ }
62
+ }
63
+ }
64
+ } catch {
65
+ /* skip parse errors */
66
+ }
67
+
68
+ return {
69
+ type: "workflow" as const,
70
+ id: w.id,
71
+ name: w.name,
72
+ status: w.status,
73
+ pattern: (() => {
74
+ try {
75
+ return JSON.parse(w.definition).pattern ?? "sequence";
76
+ } catch {
77
+ return "sequence";
78
+ }
79
+ })(),
80
+ projectName: w.projectId ? projectMap.get(w.projectId) ?? undefined : undefined,
81
+ stepProgress,
82
+ currentStepName,
83
+ createdAt: w.createdAt.toISOString(),
84
+ };
85
+ });
86
+
87
+ return (
88
+ <KanbanBoard
89
+ initialTasks={serializedTasks}
90
+ initialWorkflows={serializedWorkflows}
91
+ projects={allProjects}
92
+ />
93
+ );
56
94
  }
57
95
 
58
96
  export default function DashboardPage() {