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.
- package/README.md +44 -50
- package/package.json +1 -1
- package/public/readme/cost-usage-list.png +0 -0
- package/public/readme/dashboard-bulk-select.png +0 -0
- package/public/readme/dashboard-card-edit.png +0 -0
- package/public/readme/dashboard-create-form-ai-applied.png +0 -0
- package/public/readme/dashboard-create-form-ai-assist.png +0 -0
- package/public/readme/dashboard-create-form-empty.png +0 -0
- package/public/readme/dashboard-create-form-filled.png +0 -0
- package/public/readme/dashboard-filtered.png +0 -0
- package/public/readme/dashboard-list.png +0 -0
- package/public/readme/dashboard-workflow-confirm.png +0 -0
- package/public/readme/home-below-fold.png +0 -0
- package/public/readme/home-list.png +0 -0
- package/public/readme/inbox-list.png +0 -0
- package/public/readme/playbook-list.png +0 -0
- package/public/readme/profiles-list.png +0 -0
- package/public/readme/settings-list.png +0 -0
- package/public/readme/workflows-list.png +0 -0
- package/src/app/api/tasks/[id]/route.ts +54 -3
- package/src/app/api/workflows/[id]/route.ts +43 -4
- package/src/app/api/workflows/[id]/status/route.ts +70 -2
- package/src/app/api/workflows/from-assist/route.ts +6 -32
- package/src/app/dashboard/page.tsx +59 -21
- package/src/app/documents/[id]/page.tsx +10 -8
- package/src/app/globals.css +11 -0
- package/src/app/page.tsx +60 -3
- package/src/app/tasks/[id]/page.tsx +22 -2
- package/src/components/costs/cost-dashboard.tsx +1 -1
- package/src/components/dashboard/greeting.tsx +3 -1
- package/src/components/dashboard/priority-queue.tsx +58 -9
- package/src/components/dashboard/stats-cards.tsx +16 -2
- package/src/components/documents/document-chip-bar.tsx +183 -0
- package/src/components/documents/document-content-renderer.tsx +146 -0
- package/src/components/documents/document-detail-view.tsx +16 -239
- package/src/components/documents/image-zoom-view.tsx +60 -0
- package/src/components/documents/smart-extracted-text.tsx +47 -0
- package/src/components/documents/utils.ts +70 -0
- package/src/components/notifications/inbox-list.tsx +4 -5
- package/src/components/notifications/notification-item.tsx +72 -8
- package/src/components/notifications/pending-approval-host.tsx +7 -4
- package/src/components/playbook/playbook-detail-view.tsx +6 -4
- package/src/components/profiles/profile-browser.tsx +1 -0
- package/src/components/profiles/profile-card.tsx +16 -8
- package/src/components/profiles/profile-detail-view.tsx +6 -1
- package/src/components/shared/app-sidebar.tsx +2 -2
- package/src/components/tasks/__tests__/kanban-board-accessibility.test.tsx +1 -1
- package/src/components/tasks/ai-assist-panel.tsx +108 -78
- package/src/components/tasks/content-preview.tsx +2 -1
- package/src/components/tasks/kanban-board.tsx +57 -5
- package/src/components/tasks/kanban-column.tsx +34 -23
- package/src/components/tasks/task-bento-cell.tsx +50 -0
- package/src/components/tasks/task-bento-grid.tsx +155 -0
- package/src/components/tasks/task-card.tsx +14 -16
- package/src/components/tasks/task-chip-bar.tsx +207 -0
- package/src/components/tasks/task-detail-view.tsx +42 -190
- package/src/components/tasks/task-result-renderer.tsx +33 -0
- package/src/components/workflows/blueprint-gallery.tsx +19 -12
- package/src/components/workflows/blueprint-preview.tsx +8 -1
- package/src/components/workflows/loop-status-view.tsx +2 -8
- package/src/components/workflows/swarm-dashboard.tsx +2 -3
- package/src/components/workflows/workflow-confirmation-view.tsx +2 -7
- package/src/components/workflows/workflow-full-output.tsx +80 -0
- package/src/components/workflows/workflow-kanban-card.tsx +121 -0
- package/src/components/workflows/workflow-list.tsx +47 -42
- package/src/components/workflows/workflow-status-view.tsx +160 -20
- package/src/lib/agents/learning-session.ts +138 -18
- package/src/lib/constants/card-icons.tsx +202 -0
- package/src/lib/constants/prose-styles.ts +7 -0
- package/src/lib/constants/task-status.ts +3 -0
- package/src/lib/docs/reader.ts +8 -3
- package/src/lib/documents/context-builder.ts +41 -0
- package/src/lib/queries/chart-data.ts +20 -1
- package/src/lib/workflows/engine.ts +57 -61
- package/src/lib/workflows/types.ts +2 -0
- package/tsconfig.json +2 -1
- package/src/components/documents/document-preview.tsx +0 -68
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Stagent
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Governed AI Agent Workspace — Supervised Local Execution, Workflows, Documents, and Provider Runtimes.
|
|
4
4
|
|
|
5
|
-
[](https://nextjs.org/) [](https://react.dev/) [](https://www.typescriptlang.org/) [](https://docs.anthropic.com/) [](https://developers.openai.com/codex/app-server) [](LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/stagent) [](https://nextjs.org/) [](https://react.dev/) [](https://www.typescriptlang.org/) [](https://docs.anthropic.com/) [](https://developers.openai.com/codex/app-server) [](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.
|
|
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 —
|
|
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
|
|
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
|
|
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 —
|
|
399
|
-
|
|
400
|
-
| Category |
|
|
401
|
-
|
|
402
|
-
| **Documents** | File
|
|
403
|
-
|
|
|
404
|
-
| | Agent
|
|
405
|
-
|
|
|
406
|
-
| |
|
|
407
|
-
| **
|
|
408
|
-
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
|
413
|
-
|
|
414
|
-
|
|
|
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
|
|
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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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
|
-
|
|
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 {
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
26
|
+
// Build project name lookup
|
|
25
27
|
const projectMap = new Map(allProjects.map((p) => [p.id, p.name]));
|
|
26
28
|
|
|
27
|
-
//
|
|
29
|
+
// Fetch all workflows for kanban display
|
|
28
30
|
const allWorkflows = await db
|
|
29
|
-
.select(
|
|
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
|
|
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
|
-
|
|
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() {
|