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
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trigger evaluator — checks active triggers on row mutations
|
|
3
|
+
* and fires matching actions (create task or start workflow).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { db } from "@/lib/db";
|
|
7
|
+
import { userTableTriggers } from "@/lib/db/schema";
|
|
8
|
+
import { eq, and } from "drizzle-orm";
|
|
9
|
+
import type { FilterSpec } from "./types";
|
|
10
|
+
|
|
11
|
+
type TriggerEvent = "row_added" | "row_updated" | "row_deleted";
|
|
12
|
+
|
|
13
|
+
interface ActionConfig {
|
|
14
|
+
workflowId?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
projectId?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Evaluate and fire triggers for a table event.
|
|
22
|
+
* Called from row mutation API routes after successful writes.
|
|
23
|
+
*/
|
|
24
|
+
export async function evaluateTriggers(
|
|
25
|
+
tableId: string,
|
|
26
|
+
event: TriggerEvent,
|
|
27
|
+
rowData: Record<string, unknown>
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
// Find active triggers matching this event
|
|
30
|
+
const triggers = db
|
|
31
|
+
.select()
|
|
32
|
+
.from(userTableTriggers)
|
|
33
|
+
.where(
|
|
34
|
+
and(
|
|
35
|
+
eq(userTableTriggers.tableId, tableId),
|
|
36
|
+
eq(userTableTriggers.triggerEvent, event),
|
|
37
|
+
eq(userTableTriggers.status, "active")
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
.all();
|
|
41
|
+
|
|
42
|
+
if (triggers.length === 0) return;
|
|
43
|
+
|
|
44
|
+
for (const trigger of triggers) {
|
|
45
|
+
// Evaluate condition if present
|
|
46
|
+
if (trigger.condition) {
|
|
47
|
+
const condition = JSON.parse(trigger.condition) as FilterSpec;
|
|
48
|
+
if (!matchesCondition(rowData, condition)) continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fire the action
|
|
52
|
+
try {
|
|
53
|
+
const config = JSON.parse(trigger.actionConfig) as ActionConfig;
|
|
54
|
+
await fireAction(trigger.actionType as "run_workflow" | "create_task", config, rowData);
|
|
55
|
+
|
|
56
|
+
// Update fire count
|
|
57
|
+
db.update(userTableTriggers)
|
|
58
|
+
.set({
|
|
59
|
+
fireCount: trigger.fireCount + 1,
|
|
60
|
+
lastFiredAt: new Date(),
|
|
61
|
+
updatedAt: new Date(),
|
|
62
|
+
})
|
|
63
|
+
.where(eq(userTableTriggers.id, trigger.id))
|
|
64
|
+
.run();
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error(`[triggers] Failed to fire trigger ${trigger.id}:`, err);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if row data matches a filter condition.
|
|
73
|
+
* Reuses the same operator logic as the query builder.
|
|
74
|
+
*/
|
|
75
|
+
function matchesCondition(
|
|
76
|
+
data: Record<string, unknown>,
|
|
77
|
+
condition: FilterSpec
|
|
78
|
+
): boolean {
|
|
79
|
+
const value = data[condition.column];
|
|
80
|
+
const strValue = value == null ? "" : String(value);
|
|
81
|
+
|
|
82
|
+
switch (condition.operator) {
|
|
83
|
+
case "eq":
|
|
84
|
+
return strValue === String(condition.value);
|
|
85
|
+
case "neq":
|
|
86
|
+
return strValue !== String(condition.value);
|
|
87
|
+
case "gt":
|
|
88
|
+
return Number(value) > Number(condition.value);
|
|
89
|
+
case "gte":
|
|
90
|
+
return Number(value) >= Number(condition.value);
|
|
91
|
+
case "lt":
|
|
92
|
+
return Number(value) < Number(condition.value);
|
|
93
|
+
case "lte":
|
|
94
|
+
return Number(value) <= Number(condition.value);
|
|
95
|
+
case "contains":
|
|
96
|
+
return strValue.toLowerCase().includes(String(condition.value).toLowerCase());
|
|
97
|
+
case "starts_with":
|
|
98
|
+
return strValue.toLowerCase().startsWith(String(condition.value).toLowerCase());
|
|
99
|
+
case "in":
|
|
100
|
+
return Array.isArray(condition.value) && condition.value.includes(strValue);
|
|
101
|
+
case "is_empty":
|
|
102
|
+
return value == null || strValue === "";
|
|
103
|
+
case "is_not_empty":
|
|
104
|
+
return value != null && strValue !== "";
|
|
105
|
+
default:
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Fire a trigger action — create a task or start a workflow.
|
|
112
|
+
*/
|
|
113
|
+
async function fireAction(
|
|
114
|
+
actionType: "run_workflow" | "create_task",
|
|
115
|
+
config: ActionConfig,
|
|
116
|
+
rowData: Record<string, unknown>
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
if (actionType === "create_task") {
|
|
119
|
+
const description = config.description
|
|
120
|
+
? `${config.description}\n\nTrigger data: ${JSON.stringify(rowData, null, 2)}`
|
|
121
|
+
: `Triggered by table row change.\n\nData: ${JSON.stringify(rowData, null, 2)}`;
|
|
122
|
+
|
|
123
|
+
await fetch(`${getBaseUrl()}/api/tasks`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: { "Content-Type": "application/json" },
|
|
126
|
+
body: JSON.stringify({
|
|
127
|
+
title: config.title ?? "Triggered Task",
|
|
128
|
+
description,
|
|
129
|
+
projectId: config.projectId ?? null,
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
} else if (actionType === "run_workflow" && config.workflowId) {
|
|
133
|
+
// Start workflow execution
|
|
134
|
+
await fetch(`${getBaseUrl()}/api/workflows/${config.workflowId}/execute`, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: { "Content-Type": "application/json" },
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
context: `Table row data: ${JSON.stringify(rowData, null, 2)}`,
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getBaseUrl(): string {
|
|
145
|
+
return process.env.NEXTAUTH_URL || process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";
|
|
146
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
UserTableRow,
|
|
3
|
+
UserTableColumnRow,
|
|
4
|
+
UserTableRowRow,
|
|
5
|
+
UserTableTemplateRow,
|
|
6
|
+
} from "@/lib/db/schema";
|
|
7
|
+
import type { ColumnDataType } from "@/lib/constants/table-status";
|
|
8
|
+
|
|
9
|
+
// ── Column Definition (stored in column_schema JSON) ─────────────────
|
|
10
|
+
|
|
11
|
+
export interface ColumnDef {
|
|
12
|
+
name: string;
|
|
13
|
+
displayName: string;
|
|
14
|
+
dataType: ColumnDataType;
|
|
15
|
+
position: number;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
defaultValue?: string | null;
|
|
18
|
+
config?: ColumnConfig | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Type-specific column configuration */
|
|
22
|
+
export interface ColumnConfig {
|
|
23
|
+
/** Select column: available options */
|
|
24
|
+
options?: string[];
|
|
25
|
+
/** Computed column: formula expression */
|
|
26
|
+
formula?: string;
|
|
27
|
+
/** Computed column: formula type */
|
|
28
|
+
formulaType?: "arithmetic" | "text_concat" | "date_diff" | "conditional" | "aggregate";
|
|
29
|
+
/** Computed column: expected result type */
|
|
30
|
+
resultType?: ColumnDataType;
|
|
31
|
+
/** Computed column: column names this formula depends on */
|
|
32
|
+
dependencies?: string[];
|
|
33
|
+
/** Relation column: target table ID */
|
|
34
|
+
targetTableId?: string;
|
|
35
|
+
/** Relation column: display column name in target table */
|
|
36
|
+
displayColumn?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Filter & Sort Specs ──────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
export type FilterOperator =
|
|
42
|
+
| "eq"
|
|
43
|
+
| "neq"
|
|
44
|
+
| "gt"
|
|
45
|
+
| "gte"
|
|
46
|
+
| "lt"
|
|
47
|
+
| "lte"
|
|
48
|
+
| "contains"
|
|
49
|
+
| "starts_with"
|
|
50
|
+
| "in"
|
|
51
|
+
| "is_empty"
|
|
52
|
+
| "is_not_empty";
|
|
53
|
+
|
|
54
|
+
export interface FilterSpec {
|
|
55
|
+
column: string;
|
|
56
|
+
operator: FilterOperator;
|
|
57
|
+
value?: string | number | boolean | string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface SortSpec {
|
|
61
|
+
column: string;
|
|
62
|
+
direction: "asc" | "desc";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── Query Options ────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
export interface RowQueryOptions {
|
|
68
|
+
filters?: FilterSpec[];
|
|
69
|
+
sorts?: SortSpec[];
|
|
70
|
+
limit?: number;
|
|
71
|
+
offset?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Enriched Types (with joins) ──────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
export interface TableWithRelations extends UserTableRow {
|
|
77
|
+
projectName?: string | null;
|
|
78
|
+
columnCount: number;
|
|
79
|
+
columns?: UserTableColumnRow[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface RowWithMeta extends UserTableRowRow {
|
|
83
|
+
parsedData: Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Input Types ──────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
export interface CreateTableInput {
|
|
89
|
+
name: string;
|
|
90
|
+
description?: string | null;
|
|
91
|
+
projectId?: string | null;
|
|
92
|
+
columns?: ColumnDef[];
|
|
93
|
+
source?: "manual" | "imported" | "agent" | "template";
|
|
94
|
+
templateId?: string | null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface UpdateTableInput {
|
|
98
|
+
name?: string;
|
|
99
|
+
description?: string | null;
|
|
100
|
+
projectId?: string | null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface AddColumnInput {
|
|
104
|
+
name: string;
|
|
105
|
+
displayName: string;
|
|
106
|
+
dataType: ColumnDataType;
|
|
107
|
+
required?: boolean;
|
|
108
|
+
defaultValue?: string | null;
|
|
109
|
+
config?: ColumnConfig | null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface UpdateColumnInput {
|
|
113
|
+
displayName?: string;
|
|
114
|
+
dataType?: ColumnDataType;
|
|
115
|
+
required?: boolean;
|
|
116
|
+
defaultValue?: string | null;
|
|
117
|
+
config?: ColumnConfig | null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface AddRowInput {
|
|
121
|
+
data: Record<string, unknown>;
|
|
122
|
+
createdBy?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface UpdateRowInput {
|
|
126
|
+
data: Record<string, unknown>;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Template Types ───────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
export interface TemplateWithPreview extends UserTableTemplateRow {
|
|
132
|
+
parsedColumns: ColumnDef[];
|
|
133
|
+
parsedSampleData: Record<string, unknown>[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface CloneFromTemplateInput {
|
|
137
|
+
templateId: string;
|
|
138
|
+
name: string;
|
|
139
|
+
projectId?: string | null;
|
|
140
|
+
includeSampleData?: boolean;
|
|
141
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { COLUMN_DATA_TYPES, TABLE_SOURCES, TEMPLATE_CATEGORIES } from "@/lib/constants/table-status";
|
|
3
|
+
|
|
4
|
+
// ── Column Definition Schema ─────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
const columnConfigSchema = z.object({
|
|
7
|
+
options: z.array(z.string()).optional(),
|
|
8
|
+
formula: z.string().optional(),
|
|
9
|
+
formulaType: z.enum(["arithmetic", "text_concat", "date_diff", "conditional", "aggregate"]).optional(),
|
|
10
|
+
resultType: z.enum(COLUMN_DATA_TYPES).optional(),
|
|
11
|
+
dependencies: z.array(z.string()).optional(),
|
|
12
|
+
targetTableId: z.string().optional(),
|
|
13
|
+
displayColumn: z.string().optional(),
|
|
14
|
+
}).optional();
|
|
15
|
+
|
|
16
|
+
const columnDefSchema = z.object({
|
|
17
|
+
name: z.string().min(1).max(64),
|
|
18
|
+
displayName: z.string().min(1).max(128),
|
|
19
|
+
dataType: z.enum(COLUMN_DATA_TYPES),
|
|
20
|
+
position: z.number().int().min(0),
|
|
21
|
+
required: z.boolean().optional(),
|
|
22
|
+
defaultValue: z.string().nullable().optional(),
|
|
23
|
+
config: columnConfigSchema,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// ── Table Schemas ────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
export const createTableSchema = z.object({
|
|
29
|
+
name: z.string().min(1).max(256),
|
|
30
|
+
description: z.string().max(1024).nullable().optional(),
|
|
31
|
+
projectId: z.string().nullable().optional(),
|
|
32
|
+
columns: z.array(columnDefSchema).optional(),
|
|
33
|
+
source: z.enum(TABLE_SOURCES).optional(),
|
|
34
|
+
templateId: z.string().nullable().optional(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const updateTableSchema = z.object({
|
|
38
|
+
name: z.string().min(1).max(256).optional(),
|
|
39
|
+
description: z.string().max(1024).nullable().optional(),
|
|
40
|
+
projectId: z.string().nullable().optional(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ── Column Schemas ───────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
export const addColumnSchema = z.object({
|
|
46
|
+
name: z.string().min(1).max(64),
|
|
47
|
+
displayName: z.string().min(1).max(128),
|
|
48
|
+
dataType: z.enum(COLUMN_DATA_TYPES),
|
|
49
|
+
required: z.boolean().optional(),
|
|
50
|
+
defaultValue: z.string().nullable().optional(),
|
|
51
|
+
config: columnConfigSchema,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const updateColumnSchema = z.object({
|
|
55
|
+
displayName: z.string().min(1).max(128).optional(),
|
|
56
|
+
dataType: z.enum(COLUMN_DATA_TYPES).optional(),
|
|
57
|
+
required: z.boolean().optional(),
|
|
58
|
+
defaultValue: z.string().nullable().optional(),
|
|
59
|
+
config: columnConfigSchema,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export const reorderColumnsSchema = z.object({
|
|
63
|
+
/** Array of column IDs in the desired order */
|
|
64
|
+
columnIds: z.array(z.string()).min(1),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ── Row Schemas ──────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
export const addRowsSchema = z.object({
|
|
70
|
+
rows: z.array(
|
|
71
|
+
z.object({
|
|
72
|
+
data: z.record(z.string(), z.unknown()),
|
|
73
|
+
createdBy: z.string().optional(),
|
|
74
|
+
})
|
|
75
|
+
).min(1).max(1000),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const updateRowSchema = z.object({
|
|
79
|
+
data: z.record(z.string(), z.unknown()),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ── Query Schemas ────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
const filterOperators = [
|
|
85
|
+
"eq", "neq", "gt", "gte", "lt", "lte",
|
|
86
|
+
"contains", "starts_with", "in", "is_empty", "is_not_empty",
|
|
87
|
+
] as const;
|
|
88
|
+
|
|
89
|
+
export const filterSpecSchema = z.object({
|
|
90
|
+
column: z.string().min(1),
|
|
91
|
+
operator: z.enum(filterOperators),
|
|
92
|
+
value: z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]).optional(),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export const sortSpecSchema = z.object({
|
|
96
|
+
column: z.string().min(1),
|
|
97
|
+
direction: z.enum(["asc", "desc"]),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
export const rowQuerySchema = z.object({
|
|
101
|
+
filters: z.array(filterSpecSchema).optional(),
|
|
102
|
+
sorts: z.array(sortSpecSchema).optional(),
|
|
103
|
+
limit: z.number().int().min(1).max(1000).optional(),
|
|
104
|
+
offset: z.number().int().min(0).optional(),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ── Template Schemas ─────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
export const cloneFromTemplateSchema = z.object({
|
|
110
|
+
templateId: z.string(),
|
|
111
|
+
name: z.string().min(1).max(256),
|
|
112
|
+
projectId: z.string().nullable().optional(),
|
|
113
|
+
includeSampleData: z.boolean().optional(),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export const listTemplatesSchema = z.object({
|
|
117
|
+
category: z.enum(TEMPLATE_CATEGORIES).optional(),
|
|
118
|
+
scope: z.enum(["system", "user"]).optional(),
|
|
119
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the app root directory.
|
|
3
|
+
* - import.meta.dirname works under npx (real path to installed package)
|
|
4
|
+
* - Turbopack compiles it to /ROOT/... (virtual, doesn't exist) → fall back to process.cwd()
|
|
5
|
+
*
|
|
6
|
+
* Uses dynamic require() for fs/path to avoid bundling Node built-ins
|
|
7
|
+
* into client components (content.ts is shared with book-reader.tsx).
|
|
8
|
+
*/
|
|
9
|
+
export function getAppRoot(metaDirname: string | undefined, depth: number): string {
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
11
|
+
const { existsSync } = require("fs") as typeof import("fs");
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
13
|
+
const { join } = require("path") as typeof import("path");
|
|
14
|
+
|
|
15
|
+
if (metaDirname) {
|
|
16
|
+
const candidate = join(metaDirname, ...Array(depth).fill(".."));
|
|
17
|
+
if (existsSync(join(candidate, "package.json"))) return candidate;
|
|
18
|
+
}
|
|
19
|
+
return process.cwd();
|
|
20
|
+
}
|
|
@@ -20,3 +20,23 @@ export function getStagentBlueprintsDir(): string {
|
|
|
20
20
|
export function getStagentScreenshotsDir(): string {
|
|
21
21
|
return join(getStagentDataDir(), "screenshots");
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
export function getStagentSnapshotsDir(): string {
|
|
25
|
+
return join(getStagentDataDir(), "snapshots");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getStagentOutputsDir(): string {
|
|
29
|
+
return join(getStagentDataDir(), "outputs");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getStagentSessionsDir(): string {
|
|
33
|
+
return join(getStagentDataDir(), "sessions");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getStagentLogsDir(): string {
|
|
37
|
+
return join(getStagentDataDir(), "logs");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getStagentDocumentsDir(): string {
|
|
41
|
+
return join(getStagentDataDir(), "documents");
|
|
42
|
+
}
|
|
@@ -72,40 +72,63 @@ describe("createTaskSchema", () => {
|
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
it("accepts
|
|
75
|
+
it("accepts documentIds as optional array of strings", () => {
|
|
76
76
|
const result = createTaskSchema.safeParse({
|
|
77
77
|
title: "Test",
|
|
78
|
-
|
|
78
|
+
documentIds: ["abc-123", "def-456"],
|
|
79
79
|
});
|
|
80
80
|
expect(result.success).toBe(true);
|
|
81
81
|
if (result.success) {
|
|
82
|
-
expect(result.data.
|
|
82
|
+
expect(result.data.documentIds).toEqual(["abc-123", "def-456"]);
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
it("accepts task without
|
|
86
|
+
it("accepts task without documentIds", () => {
|
|
87
87
|
const result = createTaskSchema.safeParse({ title: "Test" });
|
|
88
88
|
expect(result.success).toBe(true);
|
|
89
89
|
if (result.success) {
|
|
90
|
-
expect(result.data.
|
|
90
|
+
expect(result.data.documentIds).toBeUndefined();
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
it("accepts empty
|
|
95
|
-
const result = createTaskSchema.safeParse({ title: "Test",
|
|
94
|
+
it("accepts empty documentIds array", () => {
|
|
95
|
+
const result = createTaskSchema.safeParse({ title: "Test", documentIds: [] });
|
|
96
96
|
expect(result.success).toBe(true);
|
|
97
97
|
if (result.success) {
|
|
98
|
-
expect(result.data.
|
|
98
|
+
expect(result.data.documentIds).toEqual([]);
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
-
it("rejects
|
|
102
|
+
it("rejects documentIds with non-string elements", () => {
|
|
103
103
|
const result = createTaskSchema.safeParse({
|
|
104
104
|
title: "Test",
|
|
105
|
-
|
|
105
|
+
documentIds: [123, true],
|
|
106
106
|
});
|
|
107
107
|
expect(result.success).toBe(false);
|
|
108
108
|
});
|
|
109
|
+
|
|
110
|
+
it("accepts deprecated fileIds and transforms to documentIds", () => {
|
|
111
|
+
const result = createTaskSchema.safeParse({
|
|
112
|
+
title: "Test",
|
|
113
|
+
fileIds: ["file-1", "file-2"],
|
|
114
|
+
});
|
|
115
|
+
expect(result.success).toBe(true);
|
|
116
|
+
if (result.success) {
|
|
117
|
+
expect(result.data.documentIds).toEqual(["file-1", "file-2"]);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("prefers documentIds over fileIds when both provided", () => {
|
|
122
|
+
const result = createTaskSchema.safeParse({
|
|
123
|
+
title: "Test",
|
|
124
|
+
documentIds: ["doc-1"],
|
|
125
|
+
fileIds: ["file-1"],
|
|
126
|
+
});
|
|
127
|
+
expect(result.success).toBe(true);
|
|
128
|
+
if (result.success) {
|
|
129
|
+
expect(result.data.documentIds).toEqual(["doc-1"]);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
109
132
|
});
|
|
110
133
|
|
|
111
134
|
describe("updateTaskSchema", () => {
|
|
@@ -141,4 +164,14 @@ describe("updateTaskSchema", () => {
|
|
|
141
164
|
});
|
|
142
165
|
expect(result.success).toBe(false);
|
|
143
166
|
});
|
|
167
|
+
|
|
168
|
+
it("accepts documentIds in update", () => {
|
|
169
|
+
const result = updateTaskSchema.safeParse({
|
|
170
|
+
documentIds: ["doc-1", "doc-2"],
|
|
171
|
+
});
|
|
172
|
+
expect(result.success).toBe(true);
|
|
173
|
+
if (result.success) {
|
|
174
|
+
expect(result.data.documentIds).toEqual(["doc-1", "doc-2"]);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
144
177
|
});
|
|
@@ -10,8 +10,13 @@ export const createTaskSchema = z.object({
|
|
|
10
10
|
priority: z.number().min(0).max(3).default(2),
|
|
11
11
|
assignedAgent: assignedAgentSchema.optional(),
|
|
12
12
|
agentProfile: z.string().optional(),
|
|
13
|
+
documentIds: z.array(z.string()).optional(),
|
|
14
|
+
/** @deprecated Use documentIds instead */
|
|
13
15
|
fileIds: z.array(z.string()).optional(),
|
|
14
|
-
})
|
|
16
|
+
}).transform((data) => ({
|
|
17
|
+
...data,
|
|
18
|
+
documentIds: data.documentIds ?? data.fileIds,
|
|
19
|
+
}));
|
|
15
20
|
|
|
16
21
|
export const updateTaskSchema = z.object({
|
|
17
22
|
title: z.string().min(1).max(200).optional(),
|
|
@@ -24,6 +29,7 @@ export const updateTaskSchema = z.object({
|
|
|
24
29
|
agentProfile: z.string().optional(),
|
|
25
30
|
result: z.string().optional(),
|
|
26
31
|
sessionId: z.string().optional(),
|
|
32
|
+
documentIds: z.array(z.string()).optional(),
|
|
27
33
|
});
|
|
28
34
|
|
|
29
35
|
export type CreateTaskInput = z.infer<typeof createTaskSchema>;
|
|
@@ -3,12 +3,12 @@ import path from "node:path";
|
|
|
3
3
|
import yaml from "js-yaml";
|
|
4
4
|
import { BlueprintSchema } from "@/lib/validators/blueprint";
|
|
5
5
|
import { getStagentBlueprintsDir } from "@/lib/utils/stagent-paths";
|
|
6
|
+
import { getAppRoot } from "@/lib/utils/app-root";
|
|
6
7
|
import type { WorkflowBlueprint } from "./types";
|
|
7
8
|
|
|
8
|
-
// Use fileURLToPath for ESM compatibility in Next.js
|
|
9
9
|
const BUILTINS_DIR = path.resolve(
|
|
10
|
-
import.meta.dirname
|
|
11
|
-
"builtins"
|
|
10
|
+
getAppRoot(import.meta.dirname, 4),
|
|
11
|
+
"src", "lib", "workflows", "blueprints", "builtins"
|
|
12
12
|
);
|
|
13
13
|
|
|
14
14
|
const USER_BLUEPRINTS_DIR = getStagentBlueprintsDir();
|
|
@@ -20,7 +20,10 @@ import {
|
|
|
20
20
|
openLearningSession,
|
|
21
21
|
closeLearningSession,
|
|
22
22
|
} from "@/lib/agents/learning-session";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
buildWorkflowDocumentContext,
|
|
25
|
+
buildPoolDocumentContext,
|
|
26
|
+
} from "@/lib/documents/context-builder";
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
29
|
* Execute a workflow by advancing through its steps according to the pattern.
|
|
@@ -359,7 +362,8 @@ async function executeParallel(
|
|
|
359
362
|
step.prompt,
|
|
360
363
|
step.assignedAgent,
|
|
361
364
|
step.agentProfile,
|
|
362
|
-
parentTaskId
|
|
365
|
+
parentTaskId,
|
|
366
|
+
step.id
|
|
363
367
|
);
|
|
364
368
|
|
|
365
369
|
const completedAt = new Date().toISOString();
|
|
@@ -433,7 +437,8 @@ async function executeParallel(
|
|
|
433
437
|
synthesisPrompt,
|
|
434
438
|
synthesisStep.assignedAgent,
|
|
435
439
|
synthesisStep.agentProfile,
|
|
436
|
-
parentTaskId
|
|
440
|
+
parentTaskId,
|
|
441
|
+
synthesisStep.id
|
|
437
442
|
);
|
|
438
443
|
|
|
439
444
|
await commitState((draft) => {
|
|
@@ -559,7 +564,8 @@ async function executeSwarm(
|
|
|
559
564
|
workerPrompt,
|
|
560
565
|
step.assignedAgent,
|
|
561
566
|
step.agentProfile,
|
|
562
|
-
parentTaskId
|
|
567
|
+
parentTaskId,
|
|
568
|
+
step.id
|
|
563
569
|
);
|
|
564
570
|
|
|
565
571
|
const completedAt = new Date().toISOString();
|
|
@@ -680,7 +686,8 @@ async function runSwarmRefinery(input: {
|
|
|
680
686
|
refineryPrompt,
|
|
681
687
|
refineryStep.assignedAgent,
|
|
682
688
|
refineryStep.agentProfile,
|
|
683
|
-
parentTaskId
|
|
689
|
+
parentTaskId,
|
|
690
|
+
refineryStep.id
|
|
684
691
|
);
|
|
685
692
|
|
|
686
693
|
refineryState.taskId = refineryResult.taskId;
|
|
@@ -716,7 +723,8 @@ export async function executeChildTask(
|
|
|
716
723
|
prompt: string,
|
|
717
724
|
assignedAgent?: string,
|
|
718
725
|
agentProfile?: string,
|
|
719
|
-
parentTaskId?: string
|
|
726
|
+
parentTaskId?: string,
|
|
727
|
+
stepId?: string
|
|
720
728
|
): Promise<{ taskId: string; status: string; result?: string; error?: string }> {
|
|
721
729
|
const [workflow] = await db
|
|
722
730
|
.select()
|
|
@@ -735,10 +743,16 @@ export async function executeChildTask(
|
|
|
735
743
|
if (parentTaskId) {
|
|
736
744
|
const docContext = await buildWorkflowDocumentContext(parentTaskId);
|
|
737
745
|
if (docContext) {
|
|
738
|
-
enrichedPrompt = `${docContext}\n\n${
|
|
746
|
+
enrichedPrompt = `${docContext}\n\n${enrichedPrompt}`;
|
|
739
747
|
}
|
|
740
748
|
}
|
|
741
749
|
|
|
750
|
+
// Inject pool document context from workflow_document_inputs junction table
|
|
751
|
+
const poolContext = await buildPoolDocumentContext(workflowId, stepId);
|
|
752
|
+
if (poolContext) {
|
|
753
|
+
enrichedPrompt = `${poolContext}\n\n${enrichedPrompt}`;
|
|
754
|
+
}
|
|
755
|
+
|
|
742
756
|
const taskId = crypto.randomUUID();
|
|
743
757
|
await db.insert(tasks).values({
|
|
744
758
|
id: taskId,
|
|
@@ -751,6 +765,7 @@ export async function executeChildTask(
|
|
|
751
765
|
priority: 1,
|
|
752
766
|
assignedAgent: assignedAgent ?? null,
|
|
753
767
|
agentProfile: resolvedProfile ?? null,
|
|
768
|
+
workflowRunNumber: workflow?.runNumber ?? null,
|
|
754
769
|
createdAt: new Date(),
|
|
755
770
|
updatedAt: new Date(),
|
|
756
771
|
});
|
|
@@ -807,7 +822,8 @@ async function executeStep(
|
|
|
807
822
|
prompt,
|
|
808
823
|
assignedAgent,
|
|
809
824
|
agentProfile,
|
|
810
|
-
parentTaskId
|
|
825
|
+
parentTaskId,
|
|
826
|
+
stepId
|
|
811
827
|
);
|
|
812
828
|
|
|
813
829
|
stepState.taskId = result.taskId;
|