stagent 0.6.2 → 0.7.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.
- package/README.md +21 -2
- package/dist/cli.js +272 -1
- package/docs/.coverage-gaps.json +66 -16
- package/docs/.last-generated +1 -1
- package/docs/features/dashboard-kanban.md +13 -7
- package/docs/features/settings.md +15 -3
- package/docs/features/tables.md +122 -0
- package/docs/index.md +3 -2
- package/docs/journeys/developer.md +26 -16
- package/docs/journeys/personal-use.md +23 -9
- package/docs/journeys/power-user.md +40 -14
- package/docs/journeys/work-use.md +43 -15
- package/docs/manifest.json +27 -17
- package/package.json +3 -2
- package/src/app/api/chat/entities/search/route.ts +12 -3
- package/src/app/api/documents/[id]/route.ts +5 -1
- package/src/app/api/documents/[id]/versions/route.ts +53 -0
- package/src/app/api/documents/route.ts +5 -1
- package/src/app/api/projects/[id]/documents/route.ts +124 -0
- package/src/app/api/projects/[id]/route.ts +72 -3
- package/src/app/api/projects/__tests__/delete-project.test.ts +13 -0
- package/src/app/api/schedules/route.ts +19 -1
- package/src/app/api/snapshots/[id]/restore/route.ts +62 -0
- package/src/app/api/snapshots/[id]/route.ts +44 -0
- package/src/app/api/snapshots/route.ts +54 -0
- package/src/app/api/snapshots/settings/route.ts +67 -0
- package/src/app/api/tables/[id]/charts/[chartId]/route.ts +89 -0
- package/src/app/api/tables/[id]/charts/route.ts +72 -0
- package/src/app/api/tables/[id]/columns/route.ts +70 -0
- package/src/app/api/tables/[id]/export/route.ts +94 -0
- package/src/app/api/tables/[id]/history/route.ts +15 -0
- package/src/app/api/tables/[id]/import/route.ts +111 -0
- package/src/app/api/tables/[id]/route.ts +86 -0
- package/src/app/api/tables/[id]/rows/[rowId]/history/route.ts +32 -0
- package/src/app/api/tables/[id]/rows/[rowId]/route.ts +51 -0
- package/src/app/api/tables/[id]/rows/route.ts +101 -0
- package/src/app/api/tables/[id]/triggers/[triggerId]/route.ts +65 -0
- package/src/app/api/tables/[id]/triggers/route.ts +122 -0
- package/src/app/api/tables/route.ts +65 -0
- package/src/app/api/tables/templates/route.ts +92 -0
- package/src/app/api/tasks/[id]/route.ts +37 -2
- package/src/app/api/tasks/[id]/siblings/route.ts +48 -0
- package/src/app/api/tasks/route.ts +8 -9
- package/src/app/api/workflows/[id]/documents/route.ts +209 -0
- package/src/app/api/workflows/[id]/execute/route.ts +6 -2
- package/src/app/api/workflows/[id]/route.ts +16 -3
- package/src/app/api/workflows/[id]/status/route.ts +18 -2
- package/src/app/api/workflows/route.ts +13 -2
- package/src/app/documents/page.tsx +5 -1
- package/src/app/layout.tsx +0 -1
- package/src/app/manifest.ts +3 -3
- package/src/app/projects/[id]/page.tsx +62 -2
- package/src/app/settings/page.tsx +2 -0
- package/src/app/tables/[id]/page.tsx +67 -0
- package/src/app/tables/page.tsx +21 -0
- package/src/app/tables/templates/page.tsx +19 -0
- package/src/components/chat/chat-table-result.tsx +139 -0
- package/src/components/documents/document-browser.tsx +1 -1
- package/src/components/documents/document-chip-bar.tsx +17 -1
- package/src/components/documents/document-detail-view.tsx +51 -0
- package/src/components/documents/document-grid.tsx +5 -0
- package/src/components/documents/document-table.tsx +4 -0
- package/src/components/documents/types.ts +3 -0
- package/src/components/projects/project-form-sheet.tsx +109 -2
- package/src/components/schedules/schedule-form.tsx +91 -1
- package/src/components/settings/data-management-section.tsx +17 -12
- package/src/components/settings/database-snapshots-section.tsx +469 -0
- package/src/components/shared/app-sidebar.tsx +2 -0
- package/src/components/shared/document-picker-sheet.tsx +486 -0
- package/src/components/tables/table-browser.tsx +234 -0
- package/src/components/tables/table-cell-editor.tsx +226 -0
- package/src/components/tables/table-chart-builder.tsx +288 -0
- package/src/components/tables/table-chart-view.tsx +146 -0
- package/src/components/tables/table-column-header.tsx +103 -0
- package/src/components/tables/table-column-sheet.tsx +331 -0
- package/src/components/tables/table-create-sheet.tsx +240 -0
- package/src/components/tables/table-detail-sheet.tsx +144 -0
- package/src/components/tables/table-detail-tabs.tsx +278 -0
- package/src/components/tables/table-grid.tsx +61 -0
- package/src/components/tables/table-history-tab.tsx +148 -0
- package/src/components/tables/table-import-wizard.tsx +542 -0
- package/src/components/tables/table-list-table.tsx +95 -0
- package/src/components/tables/table-relation-combobox.tsx +217 -0
- package/src/components/tables/table-spreadsheet.tsx +499 -0
- package/src/components/tables/table-template-gallery.tsx +162 -0
- package/src/components/tables/table-template-preview.tsx +219 -0
- package/src/components/tables/table-toolbar.tsx +79 -0
- package/src/components/tables/table-triggers-tab.tsx +446 -0
- package/src/components/tables/types.ts +6 -0
- package/src/components/tables/use-spreadsheet-keys.ts +171 -0
- package/src/components/tables/utils.ts +29 -0
- package/src/components/tasks/task-card.tsx +8 -1
- package/src/components/tasks/task-create-panel.tsx +111 -14
- package/src/components/tasks/task-detail-view.tsx +47 -0
- package/src/components/tasks/task-edit-dialog.tsx +103 -2
- package/src/components/workflows/workflow-form-view.tsx +207 -7
- package/src/components/workflows/workflow-kanban-card.tsx +8 -1
- package/src/components/workflows/workflow-list.tsx +90 -45
- package/src/components/workflows/workflow-status-view.tsx +168 -23
- package/src/instrumentation.ts +3 -0
- package/src/lib/__tests__/npx-process-cwd.test.ts +17 -2
- package/src/lib/agents/__tests__/claude-agent.test.ts +5 -1
- package/src/lib/agents/claude-agent.ts +3 -1
- package/src/lib/agents/profiles/registry.ts +6 -3
- package/src/lib/agents/runtime/anthropic-direct.ts +29 -0
- package/src/lib/agents/runtime/openai-direct.ts +29 -0
- package/src/lib/book/__tests__/chapter-slugs.test.ts +80 -0
- package/src/lib/book/chapter-generator.ts +4 -19
- package/src/lib/book/chapter-mapping.ts +17 -0
- package/src/lib/book/content.ts +5 -16
- package/src/lib/book/update-detector.ts +3 -16
- package/src/lib/chat/engine.ts +1 -0
- package/src/lib/chat/stagent-tools.ts +2 -0
- package/src/lib/chat/system-prompt.ts +9 -1
- package/src/lib/chat/tool-catalog.ts +35 -0
- package/src/lib/chat/tools/settings-tools.ts +109 -0
- package/src/lib/chat/tools/table-tools.ts +955 -0
- package/src/lib/chat/tools/workflow-tools.ts +145 -2
- package/src/lib/constants/table-status.ts +68 -0
- package/src/lib/data/__tests__/clear.test.ts +1 -1
- package/src/lib/data/clear.ts +57 -0
- package/src/lib/data/seed-data/__tests__/profiles.test.ts +28 -23
- package/src/lib/data/seed-data/conversations.ts +350 -42
- package/src/lib/data/seed-data/documents.ts +564 -591
- package/src/lib/data/seed-data/learned-context.ts +101 -22
- package/src/lib/data/seed-data/notifications.ts +344 -70
- package/src/lib/data/seed-data/profile-test-results.ts +92 -11
- package/src/lib/data/seed-data/profiles.ts +144 -46
- package/src/lib/data/seed-data/projects.ts +50 -18
- package/src/lib/data/seed-data/repo-imports.ts +28 -13
- package/src/lib/data/seed-data/schedules.ts +208 -41
- package/src/lib/data/seed-data/table-templates.ts +234 -0
- package/src/lib/data/seed-data/tasks.ts +614 -116
- package/src/lib/data/seed-data/usage-ledger.ts +182 -103
- package/src/lib/data/seed-data/user-tables.ts +203 -0
- package/src/lib/data/seed-data/views.ts +52 -7
- package/src/lib/data/seed-data/workflows.ts +231 -84
- package/src/lib/data/seed.ts +55 -14
- package/src/lib/data/tables.ts +417 -0
- package/src/lib/db/bootstrap.ts +275 -0
- package/src/lib/db/index.ts +9 -0
- package/src/lib/db/migrations/0016_add_workflow_document_inputs.sql +13 -0
- package/src/lib/db/migrations/0017_add_document_picker_tables.sql +25 -0
- package/src/lib/db/migrations/0018_add_workflow_run_number.sql +2 -0
- package/src/lib/db/migrations/0019_add_tables_feature.sql +160 -0
- package/src/lib/db/migrations/0020_add_table_triggers.sql +19 -0
- package/src/lib/db/migrations/0021_add_row_history.sql +15 -0
- package/src/lib/db/schema.ts +445 -0
- package/src/lib/docs/reader.ts +2 -3
- package/src/lib/documents/context-builder.ts +75 -2
- package/src/lib/documents/document-resolver.ts +119 -0
- package/src/lib/documents/processors/spreadsheet.ts +2 -1
- package/src/lib/schedules/scheduler.ts +31 -1
- package/src/lib/snapshots/auto-backup.ts +132 -0
- package/src/lib/snapshots/retention.ts +64 -0
- package/src/lib/snapshots/snapshot-manager.ts +429 -0
- package/src/lib/tables/computed.ts +61 -0
- package/src/lib/tables/context-builder.ts +139 -0
- package/src/lib/tables/formula-engine.ts +415 -0
- package/src/lib/tables/history.ts +115 -0
- package/src/lib/tables/import.ts +343 -0
- package/src/lib/tables/query-builder.ts +152 -0
- package/src/lib/tables/trigger-evaluator.ts +146 -0
- package/src/lib/tables/types.ts +141 -0
- package/src/lib/tables/validation.ts +119 -0
- package/src/lib/utils/app-root.ts +20 -0
- package/src/lib/utils/stagent-paths.ts +20 -0
- package/src/lib/validators/__tests__/task.test.ts +43 -10
- package/src/lib/validators/task.ts +7 -1
- package/src/lib/workflows/blueprints/registry.ts +3 -3
- package/src/lib/workflows/engine.ts +24 -8
- package/src/lib/workflows/types.ts +14 -0
- package/tsconfig.json +3 -1
- package/public/icon.svg +0 -13
- package/src/components/tasks/file-upload.tsx +0 -120
- /package/docs/features/{playbook.md → user-guide.md} +0 -0
package/docs/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generated": "2026-
|
|
3
|
-
"version":
|
|
2
|
+
"generated": "2026-04-03T22:00:00Z",
|
|
3
|
+
"version": 3,
|
|
4
4
|
"sections": [
|
|
5
5
|
{
|
|
6
6
|
"slug": "home-workspace",
|
|
@@ -72,6 +72,16 @@
|
|
|
72
72
|
"features": ["document-manager", "file-attachment-data-layer", "document-preprocessing", "agent-document-context", "document-output-generation"],
|
|
73
73
|
"screengrabCount": 2
|
|
74
74
|
},
|
|
75
|
+
{
|
|
76
|
+
"slug": "tables",
|
|
77
|
+
"title": "Tables",
|
|
78
|
+
"category": "feature-reference",
|
|
79
|
+
"path": "features/tables.md",
|
|
80
|
+
"route": "/tables",
|
|
81
|
+
"tags": ["tables", "structured-data", "spreadsheet", "charts", "triggers", "templates", "import", "export", "formulas"],
|
|
82
|
+
"features": ["tables-data-layer", "tables-list-page", "tables-spreadsheet-editor", "tables-document-import", "tables-template-gallery", "tables-agent-integration", "tables-chat-queries", "tables-computed-columns", "tables-cross-joins", "tables-agent-charts", "tables-workflow-triggers", "tables-nl-creation", "tables-export", "tables-versioning"],
|
|
83
|
+
"screengrabCount": 8
|
|
84
|
+
},
|
|
75
85
|
{
|
|
76
86
|
"slug": "monitoring",
|
|
77
87
|
"title": "Monitor",
|
|
@@ -118,8 +128,8 @@
|
|
|
118
128
|
"category": "feature-reference",
|
|
119
129
|
"path": "features/settings.md",
|
|
120
130
|
"route": "/settings",
|
|
121
|
-
"tags": ["settings", "authentication", "permissions", "presets", "budgets", "browser-tools", "ollama", "channels"],
|
|
122
|
-
"features": ["session-management", "tool-permission-persistence", "tool-permission-presets", "browser-use", "spend-budget-guardrails", "ollama-runtime-provider", "multi-channel-delivery", "bidirectional-channel-chat"],
|
|
131
|
+
"tags": ["settings", "authentication", "permissions", "presets", "budgets", "browser-tools", "ollama", "channels", "snapshots"],
|
|
132
|
+
"features": ["session-management", "tool-permission-persistence", "tool-permission-presets", "browser-use", "spend-budget-guardrails", "ollama-runtime-provider", "multi-channel-delivery", "bidirectional-channel-chat", "database-snapshot-backup"],
|
|
123
133
|
"screengrabCount": 9
|
|
124
134
|
},
|
|
125
135
|
{
|
|
@@ -136,7 +146,7 @@
|
|
|
136
146
|
"slug": "playbook",
|
|
137
147
|
"title": "User Guide",
|
|
138
148
|
"category": "feature-reference",
|
|
139
|
-
"path": "features/
|
|
149
|
+
"path": "features/user-guide.md",
|
|
140
150
|
"route": "/user-guide",
|
|
141
151
|
"tags": ["user-guide", "documentation", "adoption-tracking"],
|
|
142
152
|
"features": ["playbook-documentation", "documentation-adoption-tracking"],
|
|
@@ -232,8 +242,8 @@
|
|
|
232
242
|
"persona": "personal",
|
|
233
243
|
"difficulty": "beginner",
|
|
234
244
|
"path": "journeys/personal-use.md",
|
|
235
|
-
"sections": ["home-workspace", "chat", "dashboard-kanban", "projects", "schedules", "delivery-channels", "user-guide"],
|
|
236
|
-
"stepCount":
|
|
245
|
+
"sections": ["home-workspace", "chat", "dashboard-kanban", "projects", "tables", "schedules", "delivery-channels", "user-guide"],
|
|
246
|
+
"stepCount": 15
|
|
237
247
|
},
|
|
238
248
|
{
|
|
239
249
|
"slug": "work-use",
|
|
@@ -241,8 +251,8 @@
|
|
|
241
251
|
"persona": "work",
|
|
242
252
|
"difficulty": "intermediate",
|
|
243
253
|
"path": "journeys/work-use.md",
|
|
244
|
-
"sections": ["projects", "chat", "documents", "workflows", "schedules", "cost-usage", "inbox-notifications", "delivery-channels"],
|
|
245
|
-
"stepCount":
|
|
254
|
+
"sections": ["projects", "chat", "documents", "tables", "workflows", "schedules", "cost-usage", "inbox-notifications", "delivery-channels"],
|
|
255
|
+
"stepCount": 16
|
|
246
256
|
},
|
|
247
257
|
{
|
|
248
258
|
"slug": "power-user",
|
|
@@ -250,8 +260,8 @@
|
|
|
250
260
|
"persona": "power-user",
|
|
251
261
|
"difficulty": "advanced",
|
|
252
262
|
"path": "journeys/power-user.md",
|
|
253
|
-
"sections": ["dashboard-kanban", "profiles", "chat", "workflows", "schedules", "monitoring", "settings"],
|
|
254
|
-
"stepCount":
|
|
263
|
+
"sections": ["dashboard-kanban", "profiles", "chat", "workflows", "tables", "schedules", "monitoring", "settings"],
|
|
264
|
+
"stepCount": 16
|
|
255
265
|
},
|
|
256
266
|
{
|
|
257
267
|
"slug": "developer",
|
|
@@ -259,14 +269,14 @@
|
|
|
259
269
|
"persona": "developer",
|
|
260
270
|
"difficulty": "advanced",
|
|
261
271
|
"path": "journeys/developer.md",
|
|
262
|
-
"sections": ["settings", "environment", "chat", "monitoring", "profiles", "workflows", "schedules", "delivery-channels"],
|
|
263
|
-
"stepCount":
|
|
272
|
+
"sections": ["settings", "environment", "chat", "monitoring", "profiles", "tables", "workflows", "schedules", "delivery-channels"],
|
|
273
|
+
"stepCount": 15
|
|
264
274
|
}
|
|
265
275
|
],
|
|
266
276
|
"metadata": {
|
|
267
|
-
"totalDocs":
|
|
268
|
-
"totalScreengrabs":
|
|
269
|
-
"featuresCovered":
|
|
270
|
-
"appSections":
|
|
277
|
+
"totalDocs": 30,
|
|
278
|
+
"totalScreengrabs": 48,
|
|
279
|
+
"featuresCovered": 82,
|
|
280
|
+
"appSections": 16
|
|
271
281
|
}
|
|
272
282
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "AI Business Operating System — run your business with AI agents. Local-first, multi-provider, governed.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
"dist/",
|
|
28
28
|
"docs/",
|
|
29
29
|
"src/",
|
|
30
|
-
"public/icon.svg",
|
|
31
30
|
"public/icon-512.png",
|
|
32
31
|
"public/stagent-s-64.png",
|
|
33
32
|
"public/stagent-s-128.png",
|
|
@@ -95,6 +94,7 @@
|
|
|
95
94
|
"react-dom": "^19",
|
|
96
95
|
"react-hook-form": "^7.71.2",
|
|
97
96
|
"react-markdown": "^10.1.0",
|
|
97
|
+
"recharts": "^3.8.1",
|
|
98
98
|
"remark-gfm": "^4.0.1",
|
|
99
99
|
"sharp": "^0.34.5",
|
|
100
100
|
"smol-toml": "^1.6.1",
|
|
@@ -102,6 +102,7 @@
|
|
|
102
102
|
"sugar-high": "^1.0.0",
|
|
103
103
|
"tailwind-merge": "^3",
|
|
104
104
|
"tailwindcss": "^4",
|
|
105
|
+
"tar": "^7.5.13",
|
|
105
106
|
"tw-animate-css": "^1",
|
|
106
107
|
"typescript": "^5",
|
|
107
108
|
"zod": "^4.3.6"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { db } from "@/lib/db";
|
|
3
|
-
import { projects, tasks, workflows, documents, schedules } from "@/lib/db/schema";
|
|
3
|
+
import { projects, tasks, workflows, documents, schedules, userTables } from "@/lib/db/schema";
|
|
4
4
|
import { like, desc } from "drizzle-orm";
|
|
5
5
|
import { listProfiles } from "@/lib/agents/profiles/registry";
|
|
6
6
|
|
|
@@ -25,7 +25,7 @@ export async function GET(request: Request) {
|
|
|
25
25
|
|
|
26
26
|
const hasQuery = query.trim().length > 0;
|
|
27
27
|
const pattern = hasQuery ? `%${query}%` : "";
|
|
28
|
-
const perType = Math.max(2, Math.floor(limit /
|
|
28
|
+
const perType = Math.max(2, Math.floor(limit / 7));
|
|
29
29
|
|
|
30
30
|
const results: EntityResult[] = [];
|
|
31
31
|
|
|
@@ -45,9 +45,12 @@ export async function GET(request: Request) {
|
|
|
45
45
|
const scheduleQuery = db
|
|
46
46
|
.select({ id: schedules.id, name: schedules.name, status: schedules.status })
|
|
47
47
|
.from(schedules);
|
|
48
|
+
const tableQuery = db
|
|
49
|
+
.select({ id: userTables.id, name: userTables.name, rowCount: userTables.rowCount, source: userTables.source })
|
|
50
|
+
.from(userTables);
|
|
48
51
|
|
|
49
52
|
// Search in parallel across all entity types
|
|
50
|
-
const [projectRows, taskRows, workflowRows, documentRows, scheduleRows] =
|
|
53
|
+
const [projectRows, taskRows, workflowRows, documentRows, scheduleRows, tableRows] =
|
|
51
54
|
await Promise.all([
|
|
52
55
|
(hasQuery ? projectQuery.where(like(projects.name, pattern)) : projectQuery)
|
|
53
56
|
.orderBy(desc(projects.updatedAt))
|
|
@@ -64,6 +67,9 @@ export async function GET(request: Request) {
|
|
|
64
67
|
(hasQuery ? scheduleQuery.where(like(schedules.name, pattern)) : scheduleQuery)
|
|
65
68
|
.orderBy(desc(schedules.updatedAt))
|
|
66
69
|
.limit(perType),
|
|
70
|
+
(hasQuery ? tableQuery.where(like(userTables.name, pattern)) : tableQuery)
|
|
71
|
+
.orderBy(desc(userTables.updatedAt))
|
|
72
|
+
.limit(perType),
|
|
67
73
|
]);
|
|
68
74
|
|
|
69
75
|
for (const p of projectRows) {
|
|
@@ -81,6 +87,9 @@ export async function GET(request: Request) {
|
|
|
81
87
|
for (const s of scheduleRows) {
|
|
82
88
|
results.push({ entityType: "schedule", entityId: s.id, label: s.name, status: s.status });
|
|
83
89
|
}
|
|
90
|
+
for (const t of tableRows) {
|
|
91
|
+
results.push({ entityType: "table", entityId: t.id, label: t.name, description: `${t.rowCount ?? 0} rows · ${t.source}` });
|
|
92
|
+
}
|
|
84
93
|
|
|
85
94
|
// Search profiles in-memory (file-based registry)
|
|
86
95
|
const allProfiles = listProfiles();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { db } from "@/lib/db";
|
|
3
|
-
import { documents, tasks, projects } from "@/lib/db/schema";
|
|
3
|
+
import { documents, tasks, projects, workflows } from "@/lib/db/schema";
|
|
4
4
|
import { eq } from "drizzle-orm";
|
|
5
5
|
import { unlink } from "fs/promises";
|
|
6
6
|
import { z } from "zod/v4";
|
|
@@ -42,9 +42,13 @@ export async function GET(
|
|
|
42
42
|
updatedAt: documents.updatedAt,
|
|
43
43
|
taskTitle: tasks.title,
|
|
44
44
|
projectName: projects.name,
|
|
45
|
+
workflowId: workflows.id,
|
|
46
|
+
workflowName: workflows.name,
|
|
47
|
+
workflowRunNumber: tasks.workflowRunNumber,
|
|
45
48
|
})
|
|
46
49
|
.from(documents)
|
|
47
50
|
.leftJoin(tasks, eq(documents.taskId, tasks.id))
|
|
51
|
+
.leftJoin(workflows, eq(tasks.workflowId, workflows.id))
|
|
48
52
|
.leftJoin(projects, eq(documents.projectId, projects.id))
|
|
49
53
|
.where(eq(documents.id, id));
|
|
50
54
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { db } from "@/lib/db";
|
|
3
|
+
import { documents, tasks } from "@/lib/db/schema";
|
|
4
|
+
import { eq, and, desc } from "drizzle-orm";
|
|
5
|
+
|
|
6
|
+
export async function GET(
|
|
7
|
+
_req: NextRequest,
|
|
8
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
9
|
+
) {
|
|
10
|
+
const { id } = await params;
|
|
11
|
+
|
|
12
|
+
// First, get the document to find its originalName and projectId
|
|
13
|
+
const [doc] = await db
|
|
14
|
+
.select({
|
|
15
|
+
originalName: documents.originalName,
|
|
16
|
+
projectId: documents.projectId,
|
|
17
|
+
direction: documents.direction,
|
|
18
|
+
})
|
|
19
|
+
.from(documents)
|
|
20
|
+
.where(eq(documents.id, id));
|
|
21
|
+
|
|
22
|
+
if (!doc) {
|
|
23
|
+
return NextResponse.json({ error: "Document not found" }, { status: 404 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Only output documents have version history
|
|
27
|
+
if (doc.direction !== "output" || !doc.projectId) {
|
|
28
|
+
return NextResponse.json([]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Find all output documents with same originalName + projectId
|
|
32
|
+
const versions = await db
|
|
33
|
+
.select({
|
|
34
|
+
id: documents.id,
|
|
35
|
+
version: documents.version,
|
|
36
|
+
size: documents.size,
|
|
37
|
+
status: documents.status,
|
|
38
|
+
createdAt: documents.createdAt,
|
|
39
|
+
workflowRunNumber: tasks.workflowRunNumber,
|
|
40
|
+
})
|
|
41
|
+
.from(documents)
|
|
42
|
+
.leftJoin(tasks, eq(documents.taskId, tasks.id))
|
|
43
|
+
.where(
|
|
44
|
+
and(
|
|
45
|
+
eq(documents.originalName, doc.originalName),
|
|
46
|
+
eq(documents.projectId, doc.projectId),
|
|
47
|
+
eq(documents.direction, "output")
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
.orderBy(desc(documents.version));
|
|
51
|
+
|
|
52
|
+
return NextResponse.json(versions);
|
|
53
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { db } from "@/lib/db";
|
|
3
|
-
import { documents, tasks, projects } from "@/lib/db/schema";
|
|
3
|
+
import { documents, tasks, projects, workflows } from "@/lib/db/schema";
|
|
4
4
|
import { eq, and, like, or, desc } from "drizzle-orm";
|
|
5
5
|
import { access, stat, copyFile, mkdir } from "fs/promises";
|
|
6
6
|
import path, { basename, extname, join } from "path";
|
|
@@ -74,9 +74,13 @@ export async function GET(req: NextRequest) {
|
|
|
74
74
|
updatedAt: documents.updatedAt,
|
|
75
75
|
taskTitle: tasks.title,
|
|
76
76
|
projectName: projects.name,
|
|
77
|
+
workflowId: workflows.id,
|
|
78
|
+
workflowName: workflows.name,
|
|
79
|
+
workflowRunNumber: tasks.workflowRunNumber,
|
|
77
80
|
})
|
|
78
81
|
.from(documents)
|
|
79
82
|
.leftJoin(tasks, eq(documents.taskId, tasks.id))
|
|
83
|
+
.leftJoin(workflows, eq(tasks.workflowId, workflows.id))
|
|
80
84
|
.leftJoin(projects, eq(documents.projectId, projects.id))
|
|
81
85
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
82
86
|
.orderBy(desc(documents.createdAt));
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { db } from "@/lib/db";
|
|
3
|
+
import {
|
|
4
|
+
projectDocumentDefaults,
|
|
5
|
+
documents,
|
|
6
|
+
projects,
|
|
7
|
+
} from "@/lib/db/schema";
|
|
8
|
+
import { eq, inArray } from "drizzle-orm";
|
|
9
|
+
|
|
10
|
+
type RouteContext = { params: Promise<{ id: string }> };
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* GET /api/projects/[id]/documents
|
|
14
|
+
* List all default document bindings for a project, with document metadata.
|
|
15
|
+
*/
|
|
16
|
+
export async function GET(
|
|
17
|
+
_request: NextRequest,
|
|
18
|
+
context: RouteContext
|
|
19
|
+
) {
|
|
20
|
+
const { id: projectId } = await context.params;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const bindings = await db
|
|
24
|
+
.select()
|
|
25
|
+
.from(projectDocumentDefaults)
|
|
26
|
+
.where(eq(projectDocumentDefaults.projectId, projectId));
|
|
27
|
+
|
|
28
|
+
if (bindings.length === 0) {
|
|
29
|
+
return NextResponse.json([]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const docIds = bindings.map((b) => b.documentId);
|
|
33
|
+
const docs = await db
|
|
34
|
+
.select()
|
|
35
|
+
.from(documents)
|
|
36
|
+
.where(inArray(documents.id, docIds));
|
|
37
|
+
|
|
38
|
+
// Return flat document list (same shape as /api/documents)
|
|
39
|
+
return NextResponse.json(
|
|
40
|
+
docs.map((doc) => ({
|
|
41
|
+
id: doc.id,
|
|
42
|
+
originalName: doc.originalName,
|
|
43
|
+
filename: doc.filename,
|
|
44
|
+
mimeType: doc.mimeType,
|
|
45
|
+
size: doc.size,
|
|
46
|
+
direction: doc.direction,
|
|
47
|
+
status: doc.status,
|
|
48
|
+
category: doc.category,
|
|
49
|
+
}))
|
|
50
|
+
);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error("[project-documents] GET failed:", error);
|
|
53
|
+
return NextResponse.json(
|
|
54
|
+
{ error: "Failed to fetch project documents" },
|
|
55
|
+
{ status: 500 }
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* PUT /api/projects/[id]/documents
|
|
62
|
+
* Replace all default document bindings for a project.
|
|
63
|
+
* Body: { documentIds: string[] }
|
|
64
|
+
*/
|
|
65
|
+
export async function PUT(
|
|
66
|
+
request: NextRequest,
|
|
67
|
+
context: RouteContext
|
|
68
|
+
) {
|
|
69
|
+
const { id: projectId } = await context.params;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const body = await request.json();
|
|
73
|
+
const { documentIds } = body as { documentIds: string[] };
|
|
74
|
+
|
|
75
|
+
if (!Array.isArray(documentIds)) {
|
|
76
|
+
return NextResponse.json(
|
|
77
|
+
{ error: "documentIds must be an array" },
|
|
78
|
+
{ status: 400 }
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Verify project exists
|
|
83
|
+
const [project] = await db
|
|
84
|
+
.select({ id: projects.id })
|
|
85
|
+
.from(projects)
|
|
86
|
+
.where(eq(projects.id, projectId));
|
|
87
|
+
|
|
88
|
+
if (!project) {
|
|
89
|
+
return NextResponse.json(
|
|
90
|
+
{ error: "Project not found" },
|
|
91
|
+
{ status: 404 }
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Remove all existing bindings
|
|
96
|
+
await db
|
|
97
|
+
.delete(projectDocumentDefaults)
|
|
98
|
+
.where(eq(projectDocumentDefaults.projectId, projectId));
|
|
99
|
+
|
|
100
|
+
// Insert new bindings
|
|
101
|
+
const now = new Date();
|
|
102
|
+
for (const docId of documentIds) {
|
|
103
|
+
try {
|
|
104
|
+
await db.insert(projectDocumentDefaults).values({
|
|
105
|
+
id: crypto.randomUUID(),
|
|
106
|
+
projectId,
|
|
107
|
+
documentId: docId,
|
|
108
|
+
createdAt: now,
|
|
109
|
+
});
|
|
110
|
+
} catch (err) {
|
|
111
|
+
const msg = err instanceof Error ? err.message : "";
|
|
112
|
+
if (!msg.includes("UNIQUE constraint")) throw err;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return NextResponse.json({ updated: documentIds.length, projectId });
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error("[project-documents] PUT failed:", error);
|
|
119
|
+
return NextResponse.json(
|
|
120
|
+
{ error: "Failed to update project documents" },
|
|
121
|
+
{ status: 500 }
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -16,6 +16,19 @@ import {
|
|
|
16
16
|
environmentScans,
|
|
17
17
|
chatMessages,
|
|
18
18
|
conversations,
|
|
19
|
+
projectDocumentDefaults,
|
|
20
|
+
userTables,
|
|
21
|
+
userTableColumns,
|
|
22
|
+
userTableRows,
|
|
23
|
+
userTableViews,
|
|
24
|
+
userTableImports,
|
|
25
|
+
userTableRelationships,
|
|
26
|
+
tableDocumentInputs,
|
|
27
|
+
taskTableInputs,
|
|
28
|
+
workflowTableInputs,
|
|
29
|
+
scheduleTableInputs,
|
|
30
|
+
userTableTriggers,
|
|
31
|
+
userTableRowHistory,
|
|
19
32
|
} from "@/lib/db/schema";
|
|
20
33
|
import { eq, inArray } from "drizzle-orm";
|
|
21
34
|
import { updateProjectSchema } from "@/lib/validators/project";
|
|
@@ -42,16 +55,44 @@ export async function PATCH(
|
|
|
42
55
|
) {
|
|
43
56
|
const { id } = await params;
|
|
44
57
|
const body = await req.json();
|
|
45
|
-
|
|
58
|
+
// Extract documentIds before validation (not a project column)
|
|
59
|
+
const { documentIds, ...projectBody } = body as Record<string, unknown> & { documentIds?: string[] };
|
|
60
|
+
const parsed = updateProjectSchema.safeParse(projectBody);
|
|
46
61
|
if (!parsed.success) {
|
|
47
62
|
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
|
|
48
63
|
}
|
|
49
64
|
|
|
65
|
+
const now = new Date();
|
|
50
66
|
await db
|
|
51
67
|
.update(projects)
|
|
52
|
-
.set({ ...parsed.data, updatedAt:
|
|
68
|
+
.set({ ...parsed.data, updatedAt: now })
|
|
53
69
|
.where(eq(projects.id, id));
|
|
54
70
|
|
|
71
|
+
// Handle default document bindings
|
|
72
|
+
if (documentIds !== undefined) {
|
|
73
|
+
try {
|
|
74
|
+
// Replace all bindings
|
|
75
|
+
await db
|
|
76
|
+
.delete(projectDocumentDefaults)
|
|
77
|
+
.where(eq(projectDocumentDefaults.projectId, id));
|
|
78
|
+
for (const docId of documentIds) {
|
|
79
|
+
try {
|
|
80
|
+
await db.insert(projectDocumentDefaults).values({
|
|
81
|
+
id: crypto.randomUUID(),
|
|
82
|
+
projectId: id,
|
|
83
|
+
documentId: docId,
|
|
84
|
+
createdAt: now,
|
|
85
|
+
});
|
|
86
|
+
} catch (err) {
|
|
87
|
+
const msg = err instanceof Error ? err.message : "";
|
|
88
|
+
if (!msg.includes("UNIQUE constraint")) throw err;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error("[projects] Document defaults update failed:", err);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
55
96
|
const [updated] = await db
|
|
56
97
|
.select()
|
|
57
98
|
.from(projects)
|
|
@@ -160,7 +201,35 @@ export async function DELETE(
|
|
|
160
201
|
.run();
|
|
161
202
|
}
|
|
162
203
|
|
|
163
|
-
// 6.
|
|
204
|
+
// 6. Project document defaults (junction table)
|
|
205
|
+
db.delete(projectDocumentDefaults).where(eq(projectDocumentDefaults.projectId, id)).run();
|
|
206
|
+
|
|
207
|
+
// 6b. User-defined tables — cascade-delete children before parent
|
|
208
|
+
const tableIds = db
|
|
209
|
+
.select({ id: userTables.id })
|
|
210
|
+
.from(userTables)
|
|
211
|
+
.where(eq(userTables.projectId, id))
|
|
212
|
+
.all()
|
|
213
|
+
.map((r) => r.id);
|
|
214
|
+
|
|
215
|
+
if (tableIds.length > 0) {
|
|
216
|
+
// Junction tables first
|
|
217
|
+
db.delete(tableDocumentInputs).where(inArray(tableDocumentInputs.tableId, tableIds)).run();
|
|
218
|
+
db.delete(taskTableInputs).where(inArray(taskTableInputs.tableId, tableIds)).run();
|
|
219
|
+
db.delete(workflowTableInputs).where(inArray(workflowTableInputs.tableId, tableIds)).run();
|
|
220
|
+
db.delete(scheduleTableInputs).where(inArray(scheduleTableInputs.tableId, tableIds)).run();
|
|
221
|
+
// Children
|
|
222
|
+
db.delete(userTableRowHistory).where(inArray(userTableRowHistory.tableId, tableIds)).run();
|
|
223
|
+
db.delete(userTableTriggers).where(inArray(userTableTriggers.tableId, tableIds)).run();
|
|
224
|
+
db.delete(userTableImports).where(inArray(userTableImports.tableId, tableIds)).run();
|
|
225
|
+
db.delete(userTableViews).where(inArray(userTableViews.tableId, tableIds)).run();
|
|
226
|
+
db.delete(userTableRelationships).where(inArray(userTableRelationships.fromTableId, tableIds)).run();
|
|
227
|
+
db.delete(userTableRows).where(inArray(userTableRows.tableId, tableIds)).run();
|
|
228
|
+
db.delete(userTableColumns).where(inArray(userTableColumns.tableId, tableIds)).run();
|
|
229
|
+
db.delete(userTables).where(inArray(userTables.id, tableIds)).run();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 7. Direct project children
|
|
164
233
|
db.delete(documents).where(eq(documents.projectId, id)).run();
|
|
165
234
|
db.delete(tasks).where(eq(tasks.projectId, id)).run();
|
|
166
235
|
if (workflowIds.length > 0) {
|
|
@@ -27,6 +27,8 @@ describe("project DELETE cascade coverage", () => {
|
|
|
27
27
|
"environmentScans",
|
|
28
28
|
"environmentCheckpoints",
|
|
29
29
|
"conversations",
|
|
30
|
+
"projectDocumentDefaults",
|
|
31
|
+
"userTables",
|
|
30
32
|
];
|
|
31
33
|
|
|
32
34
|
// Tables that are indirect children (FK to a table that has projectId)
|
|
@@ -38,6 +40,17 @@ describe("project DELETE cascade coverage", () => {
|
|
|
38
40
|
{ table: "chatMessages", parent: "conversations", via: "conversationId" },
|
|
39
41
|
{ table: "environmentArtifacts", parent: "environmentScans", via: "scanId" },
|
|
40
42
|
{ table: "environmentSyncOps", parent: "environmentCheckpoints", via: "checkpointId" },
|
|
43
|
+
{ table: "userTableColumns", parent: "userTables", via: "tableId" },
|
|
44
|
+
{ table: "userTableRows", parent: "userTables", via: "tableId" },
|
|
45
|
+
{ table: "userTableViews", parent: "userTables", via: "tableId" },
|
|
46
|
+
{ table: "userTableImports", parent: "userTables", via: "tableId" },
|
|
47
|
+
{ table: "userTableRelationships", parent: "userTables", via: "fromTableId" },
|
|
48
|
+
{ table: "tableDocumentInputs", parent: "userTables", via: "tableId" },
|
|
49
|
+
{ table: "taskTableInputs", parent: "userTables", via: "tableId" },
|
|
50
|
+
{ table: "workflowTableInputs", parent: "userTables", via: "tableId" },
|
|
51
|
+
{ table: "scheduleTableInputs", parent: "userTables", via: "tableId" },
|
|
52
|
+
{ table: "userTableTriggers", parent: "userTables", via: "tableId" },
|
|
53
|
+
{ table: "userTableRowHistory", parent: "userTables", via: "tableId" },
|
|
41
54
|
];
|
|
42
55
|
|
|
43
56
|
it("handles all tables with direct projectId FK", () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { db } from "@/lib/db";
|
|
3
|
-
import { schedules } from "@/lib/db/schema";
|
|
3
|
+
import { schedules, scheduleDocumentInputs } from "@/lib/db/schema";
|
|
4
4
|
import { desc, eq } from "drizzle-orm";
|
|
5
5
|
import { parseInterval, computeNextFireTime } from "@/lib/schedules/interval-parser";
|
|
6
6
|
import { parseNaturalLanguage } from "@/lib/schedules/nlp-parser";
|
|
@@ -34,6 +34,7 @@ export async function POST(req: NextRequest) {
|
|
|
34
34
|
activeHoursEnd,
|
|
35
35
|
activeTimezone,
|
|
36
36
|
heartbeatBudgetPerDay,
|
|
37
|
+
documentIds,
|
|
37
38
|
} =
|
|
38
39
|
body as {
|
|
39
40
|
name?: string;
|
|
@@ -51,6 +52,7 @@ export async function POST(req: NextRequest) {
|
|
|
51
52
|
activeHoursEnd?: number;
|
|
52
53
|
activeTimezone?: string;
|
|
53
54
|
heartbeatBudgetPerDay?: number;
|
|
55
|
+
documentIds?: string[];
|
|
54
56
|
};
|
|
55
57
|
|
|
56
58
|
const scheduleType = type ?? "scheduled";
|
|
@@ -167,6 +169,22 @@ export async function POST(req: NextRequest) {
|
|
|
167
169
|
updatedAt: now,
|
|
168
170
|
});
|
|
169
171
|
|
|
172
|
+
// Link documents to schedule
|
|
173
|
+
if (documentIds && documentIds.length > 0) {
|
|
174
|
+
try {
|
|
175
|
+
for (const docId of documentIds) {
|
|
176
|
+
await db.insert(scheduleDocumentInputs).values({
|
|
177
|
+
id: crypto.randomUUID(),
|
|
178
|
+
scheduleId: id,
|
|
179
|
+
documentId: docId,
|
|
180
|
+
createdAt: now,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.error("[schedules] Document association failed:", err);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
170
188
|
const [created] = await db
|
|
171
189
|
.select()
|
|
172
190
|
.from(schedules)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import {
|
|
3
|
+
restoreFromSnapshot,
|
|
4
|
+
isSnapshotLocked,
|
|
5
|
+
} from "@/lib/snapshots/snapshot-manager";
|
|
6
|
+
import { db } from "@/lib/db";
|
|
7
|
+
import { tasks } from "@/lib/db/schema";
|
|
8
|
+
import { eq } from "drizzle-orm";
|
|
9
|
+
|
|
10
|
+
/** POST /api/snapshots/[id]/restore — restore from a snapshot (destructive) */
|
|
11
|
+
export async function POST(
|
|
12
|
+
_req: NextRequest,
|
|
13
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
14
|
+
) {
|
|
15
|
+
const { id } = await params;
|
|
16
|
+
|
|
17
|
+
if (isSnapshotLocked()) {
|
|
18
|
+
return NextResponse.json(
|
|
19
|
+
{ error: "Another snapshot operation is already in progress" },
|
|
20
|
+
{ status: 409 }
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check for running tasks
|
|
25
|
+
const runningTasks = await db
|
|
26
|
+
.select()
|
|
27
|
+
.from(tasks)
|
|
28
|
+
.where(eq(tasks.status, "running"));
|
|
29
|
+
|
|
30
|
+
if (runningTasks.length > 0) {
|
|
31
|
+
return NextResponse.json(
|
|
32
|
+
{
|
|
33
|
+
error: `${runningTasks.length} task(s) are currently running. Stop them before restoring.`,
|
|
34
|
+
runningTasks: runningTasks.map((t) => ({
|
|
35
|
+
id: t.id,
|
|
36
|
+
title: t.title,
|
|
37
|
+
})),
|
|
38
|
+
},
|
|
39
|
+
{ status: 409 }
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const result = await restoreFromSnapshot(id);
|
|
45
|
+
|
|
46
|
+
return NextResponse.json({
|
|
47
|
+
success: true,
|
|
48
|
+
requiresRestart: result.requiresRestart,
|
|
49
|
+
preRestoreSnapshotId: result.preRestoreSnapshotId,
|
|
50
|
+
message:
|
|
51
|
+
"Restore complete. Please restart the server to load the restored database.",
|
|
52
|
+
});
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return NextResponse.json(
|
|
55
|
+
{
|
|
56
|
+
error:
|
|
57
|
+
error instanceof Error ? error.message : "Failed to restore snapshot",
|
|
58
|
+
},
|
|
59
|
+
{ status: 500 }
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|