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.
Files changed (176) hide show
  1. package/README.md +21 -2
  2. package/dist/cli.js +272 -1
  3. package/docs/.coverage-gaps.json +66 -16
  4. package/docs/.last-generated +1 -1
  5. package/docs/features/dashboard-kanban.md +13 -7
  6. package/docs/features/settings.md +15 -3
  7. package/docs/features/tables.md +122 -0
  8. package/docs/index.md +3 -2
  9. package/docs/journeys/developer.md +26 -16
  10. package/docs/journeys/personal-use.md +23 -9
  11. package/docs/journeys/power-user.md +40 -14
  12. package/docs/journeys/work-use.md +43 -15
  13. package/docs/manifest.json +27 -17
  14. package/package.json +3 -2
  15. package/src/app/api/chat/entities/search/route.ts +12 -3
  16. package/src/app/api/documents/[id]/route.ts +5 -1
  17. package/src/app/api/documents/[id]/versions/route.ts +53 -0
  18. package/src/app/api/documents/route.ts +5 -1
  19. package/src/app/api/projects/[id]/documents/route.ts +124 -0
  20. package/src/app/api/projects/[id]/route.ts +72 -3
  21. package/src/app/api/projects/__tests__/delete-project.test.ts +13 -0
  22. package/src/app/api/schedules/route.ts +19 -1
  23. package/src/app/api/snapshots/[id]/restore/route.ts +62 -0
  24. package/src/app/api/snapshots/[id]/route.ts +44 -0
  25. package/src/app/api/snapshots/route.ts +54 -0
  26. package/src/app/api/snapshots/settings/route.ts +67 -0
  27. package/src/app/api/tables/[id]/charts/[chartId]/route.ts +89 -0
  28. package/src/app/api/tables/[id]/charts/route.ts +72 -0
  29. package/src/app/api/tables/[id]/columns/route.ts +70 -0
  30. package/src/app/api/tables/[id]/export/route.ts +94 -0
  31. package/src/app/api/tables/[id]/history/route.ts +15 -0
  32. package/src/app/api/tables/[id]/import/route.ts +111 -0
  33. package/src/app/api/tables/[id]/route.ts +86 -0
  34. package/src/app/api/tables/[id]/rows/[rowId]/history/route.ts +32 -0
  35. package/src/app/api/tables/[id]/rows/[rowId]/route.ts +51 -0
  36. package/src/app/api/tables/[id]/rows/route.ts +101 -0
  37. package/src/app/api/tables/[id]/triggers/[triggerId]/route.ts +65 -0
  38. package/src/app/api/tables/[id]/triggers/route.ts +122 -0
  39. package/src/app/api/tables/route.ts +65 -0
  40. package/src/app/api/tables/templates/route.ts +92 -0
  41. package/src/app/api/tasks/[id]/route.ts +37 -2
  42. package/src/app/api/tasks/[id]/siblings/route.ts +48 -0
  43. package/src/app/api/tasks/route.ts +8 -9
  44. package/src/app/api/workflows/[id]/documents/route.ts +209 -0
  45. package/src/app/api/workflows/[id]/execute/route.ts +6 -2
  46. package/src/app/api/workflows/[id]/route.ts +16 -3
  47. package/src/app/api/workflows/[id]/status/route.ts +18 -2
  48. package/src/app/api/workflows/route.ts +13 -2
  49. package/src/app/documents/page.tsx +5 -1
  50. package/src/app/layout.tsx +0 -1
  51. package/src/app/manifest.ts +3 -3
  52. package/src/app/projects/[id]/page.tsx +62 -2
  53. package/src/app/settings/page.tsx +2 -0
  54. package/src/app/tables/[id]/page.tsx +67 -0
  55. package/src/app/tables/page.tsx +21 -0
  56. package/src/app/tables/templates/page.tsx +19 -0
  57. package/src/components/chat/chat-table-result.tsx +139 -0
  58. package/src/components/documents/document-browser.tsx +1 -1
  59. package/src/components/documents/document-chip-bar.tsx +17 -1
  60. package/src/components/documents/document-detail-view.tsx +51 -0
  61. package/src/components/documents/document-grid.tsx +5 -0
  62. package/src/components/documents/document-table.tsx +4 -0
  63. package/src/components/documents/types.ts +3 -0
  64. package/src/components/projects/project-form-sheet.tsx +109 -2
  65. package/src/components/schedules/schedule-form.tsx +91 -1
  66. package/src/components/settings/data-management-section.tsx +17 -12
  67. package/src/components/settings/database-snapshots-section.tsx +469 -0
  68. package/src/components/shared/app-sidebar.tsx +2 -0
  69. package/src/components/shared/document-picker-sheet.tsx +486 -0
  70. package/src/components/tables/table-browser.tsx +234 -0
  71. package/src/components/tables/table-cell-editor.tsx +226 -0
  72. package/src/components/tables/table-chart-builder.tsx +288 -0
  73. package/src/components/tables/table-chart-view.tsx +146 -0
  74. package/src/components/tables/table-column-header.tsx +103 -0
  75. package/src/components/tables/table-column-sheet.tsx +331 -0
  76. package/src/components/tables/table-create-sheet.tsx +240 -0
  77. package/src/components/tables/table-detail-sheet.tsx +144 -0
  78. package/src/components/tables/table-detail-tabs.tsx +278 -0
  79. package/src/components/tables/table-grid.tsx +61 -0
  80. package/src/components/tables/table-history-tab.tsx +148 -0
  81. package/src/components/tables/table-import-wizard.tsx +542 -0
  82. package/src/components/tables/table-list-table.tsx +95 -0
  83. package/src/components/tables/table-relation-combobox.tsx +217 -0
  84. package/src/components/tables/table-spreadsheet.tsx +499 -0
  85. package/src/components/tables/table-template-gallery.tsx +162 -0
  86. package/src/components/tables/table-template-preview.tsx +219 -0
  87. package/src/components/tables/table-toolbar.tsx +79 -0
  88. package/src/components/tables/table-triggers-tab.tsx +446 -0
  89. package/src/components/tables/types.ts +6 -0
  90. package/src/components/tables/use-spreadsheet-keys.ts +171 -0
  91. package/src/components/tables/utils.ts +29 -0
  92. package/src/components/tasks/task-card.tsx +8 -1
  93. package/src/components/tasks/task-create-panel.tsx +111 -14
  94. package/src/components/tasks/task-detail-view.tsx +47 -0
  95. package/src/components/tasks/task-edit-dialog.tsx +103 -2
  96. package/src/components/workflows/workflow-form-view.tsx +207 -7
  97. package/src/components/workflows/workflow-kanban-card.tsx +8 -1
  98. package/src/components/workflows/workflow-list.tsx +90 -45
  99. package/src/components/workflows/workflow-status-view.tsx +168 -23
  100. package/src/instrumentation.ts +3 -0
  101. package/src/lib/__tests__/npx-process-cwd.test.ts +17 -2
  102. package/src/lib/agents/__tests__/claude-agent.test.ts +5 -1
  103. package/src/lib/agents/claude-agent.ts +3 -1
  104. package/src/lib/agents/profiles/registry.ts +6 -3
  105. package/src/lib/agents/runtime/anthropic-direct.ts +29 -0
  106. package/src/lib/agents/runtime/openai-direct.ts +29 -0
  107. package/src/lib/book/__tests__/chapter-slugs.test.ts +80 -0
  108. package/src/lib/book/chapter-generator.ts +4 -19
  109. package/src/lib/book/chapter-mapping.ts +17 -0
  110. package/src/lib/book/content.ts +5 -16
  111. package/src/lib/book/update-detector.ts +3 -16
  112. package/src/lib/chat/engine.ts +1 -0
  113. package/src/lib/chat/stagent-tools.ts +2 -0
  114. package/src/lib/chat/system-prompt.ts +9 -1
  115. package/src/lib/chat/tool-catalog.ts +35 -0
  116. package/src/lib/chat/tools/settings-tools.ts +109 -0
  117. package/src/lib/chat/tools/table-tools.ts +955 -0
  118. package/src/lib/chat/tools/workflow-tools.ts +145 -2
  119. package/src/lib/constants/table-status.ts +68 -0
  120. package/src/lib/data/__tests__/clear.test.ts +1 -1
  121. package/src/lib/data/clear.ts +57 -0
  122. package/src/lib/data/seed-data/__tests__/profiles.test.ts +28 -23
  123. package/src/lib/data/seed-data/conversations.ts +350 -42
  124. package/src/lib/data/seed-data/documents.ts +564 -591
  125. package/src/lib/data/seed-data/learned-context.ts +101 -22
  126. package/src/lib/data/seed-data/notifications.ts +344 -70
  127. package/src/lib/data/seed-data/profile-test-results.ts +92 -11
  128. package/src/lib/data/seed-data/profiles.ts +144 -46
  129. package/src/lib/data/seed-data/projects.ts +50 -18
  130. package/src/lib/data/seed-data/repo-imports.ts +28 -13
  131. package/src/lib/data/seed-data/schedules.ts +208 -41
  132. package/src/lib/data/seed-data/table-templates.ts +234 -0
  133. package/src/lib/data/seed-data/tasks.ts +614 -116
  134. package/src/lib/data/seed-data/usage-ledger.ts +182 -103
  135. package/src/lib/data/seed-data/user-tables.ts +203 -0
  136. package/src/lib/data/seed-data/views.ts +52 -7
  137. package/src/lib/data/seed-data/workflows.ts +231 -84
  138. package/src/lib/data/seed.ts +55 -14
  139. package/src/lib/data/tables.ts +417 -0
  140. package/src/lib/db/bootstrap.ts +275 -0
  141. package/src/lib/db/index.ts +9 -0
  142. package/src/lib/db/migrations/0016_add_workflow_document_inputs.sql +13 -0
  143. package/src/lib/db/migrations/0017_add_document_picker_tables.sql +25 -0
  144. package/src/lib/db/migrations/0018_add_workflow_run_number.sql +2 -0
  145. package/src/lib/db/migrations/0019_add_tables_feature.sql +160 -0
  146. package/src/lib/db/migrations/0020_add_table_triggers.sql +19 -0
  147. package/src/lib/db/migrations/0021_add_row_history.sql +15 -0
  148. package/src/lib/db/schema.ts +445 -0
  149. package/src/lib/docs/reader.ts +2 -3
  150. package/src/lib/documents/context-builder.ts +75 -2
  151. package/src/lib/documents/document-resolver.ts +119 -0
  152. package/src/lib/documents/processors/spreadsheet.ts +2 -1
  153. package/src/lib/schedules/scheduler.ts +31 -1
  154. package/src/lib/snapshots/auto-backup.ts +132 -0
  155. package/src/lib/snapshots/retention.ts +64 -0
  156. package/src/lib/snapshots/snapshot-manager.ts +429 -0
  157. package/src/lib/tables/computed.ts +61 -0
  158. package/src/lib/tables/context-builder.ts +139 -0
  159. package/src/lib/tables/formula-engine.ts +415 -0
  160. package/src/lib/tables/history.ts +115 -0
  161. package/src/lib/tables/import.ts +343 -0
  162. package/src/lib/tables/query-builder.ts +152 -0
  163. package/src/lib/tables/trigger-evaluator.ts +146 -0
  164. package/src/lib/tables/types.ts +141 -0
  165. package/src/lib/tables/validation.ts +119 -0
  166. package/src/lib/utils/app-root.ts +20 -0
  167. package/src/lib/utils/stagent-paths.ts +20 -0
  168. package/src/lib/validators/__tests__/task.test.ts +43 -10
  169. package/src/lib/validators/task.ts +7 -1
  170. package/src/lib/workflows/blueprints/registry.ts +3 -3
  171. package/src/lib/workflows/engine.ts +24 -8
  172. package/src/lib/workflows/types.ts +14 -0
  173. package/tsconfig.json +3 -1
  174. package/public/icon.svg +0 -13
  175. package/src/components/tasks/file-upload.tsx +0 -120
  176. /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 fileIds as optional array of strings", () => {
75
+ it("accepts documentIds as optional array of strings", () => {
76
76
  const result = createTaskSchema.safeParse({
77
77
  title: "Test",
78
- fileIds: ["abc-123", "def-456"],
78
+ documentIds: ["abc-123", "def-456"],
79
79
  });
80
80
  expect(result.success).toBe(true);
81
81
  if (result.success) {
82
- expect(result.data.fileIds).toEqual(["abc-123", "def-456"]);
82
+ expect(result.data.documentIds).toEqual(["abc-123", "def-456"]);
83
83
  }
84
84
  });
85
85
 
86
- it("accepts task without fileIds", () => {
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.fileIds).toBeUndefined();
90
+ expect(result.data.documentIds).toBeUndefined();
91
91
  }
92
92
  });
93
93
 
94
- it("accepts empty fileIds array", () => {
95
- const result = createTaskSchema.safeParse({ title: "Test", fileIds: [] });
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.fileIds).toEqual([]);
98
+ expect(result.data.documentIds).toEqual([]);
99
99
  }
100
100
  });
101
101
 
102
- it("rejects fileIds with non-string elements", () => {
102
+ it("rejects documentIds with non-string elements", () => {
103
103
  const result = createTaskSchema.safeParse({
104
104
  title: "Test",
105
- fileIds: [123, true],
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 ?? path.dirname(new URL(import.meta.url).pathname),
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 { buildWorkflowDocumentContext } from "@/lib/documents/context-builder";
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${prompt}`;
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;