stagent 0.1.0 → 0.1.2

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 (64) hide show
  1. package/README.md +33 -30
  2. package/dist/cli.js +376 -49
  3. package/package.json +23 -24
  4. package/public/desktop-icon-512.png +0 -0
  5. package/public/icon-512.png +0 -0
  6. package/src/app/api/data/clear/route.ts +0 -7
  7. package/src/app/api/data/seed/route.ts +0 -7
  8. package/src/app/api/profiles/[id]/context/route.ts +109 -0
  9. package/src/components/dashboard/__tests__/accessibility.test.tsx +42 -0
  10. package/src/components/documents/__tests__/document-upload-dialog.test.tsx +46 -0
  11. package/src/components/notifications/__tests__/pending-approval-host.test.tsx +122 -0
  12. package/src/components/notifications/__tests__/permission-response-actions.test.tsx +79 -0
  13. package/src/components/notifications/pending-approval-host.tsx +49 -25
  14. package/src/components/profiles/context-proposal-review.tsx +145 -0
  15. package/src/components/profiles/learned-context-panel.tsx +286 -0
  16. package/src/components/profiles/profile-detail-view.tsx +4 -0
  17. package/src/components/projects/__tests__/dialog-focus.test.tsx +87 -0
  18. package/src/components/tasks/__tests__/kanban-board-accessibility.test.tsx +59 -0
  19. package/src/lib/__tests__/setup-verify.test.ts +28 -0
  20. package/src/lib/__tests__/utils.test.ts +29 -0
  21. package/src/lib/agents/__tests__/claude-agent.test.ts +946 -0
  22. package/src/lib/agents/__tests__/execution-manager.test.ts +63 -0
  23. package/src/lib/agents/__tests__/router.test.ts +61 -0
  24. package/src/lib/agents/claude-agent.ts +34 -5
  25. package/src/lib/agents/learned-context.ts +322 -0
  26. package/src/lib/agents/pattern-extractor.ts +150 -0
  27. package/src/lib/agents/profiles/__tests__/compatibility.test.ts +76 -0
  28. package/src/lib/agents/profiles/__tests__/registry.test.ts +177 -0
  29. package/src/lib/agents/profiles/builtins/sweep/SKILL.md +47 -0
  30. package/src/lib/agents/profiles/builtins/sweep/profile.yaml +12 -0
  31. package/src/lib/agents/runtime/__tests__/catalog.test.ts +38 -0
  32. package/src/lib/agents/runtime/openai-codex.ts +1 -1
  33. package/src/lib/agents/sweep.ts +65 -0
  34. package/src/lib/constants/__tests__/task-status.test.ts +119 -0
  35. package/src/lib/data/seed-data/__tests__/profiles.test.ts +141 -0
  36. package/src/lib/db/__tests__/bootstrap.test.ts +56 -0
  37. package/src/lib/db/bootstrap.ts +301 -0
  38. package/src/lib/db/index.ts +2 -205
  39. package/src/lib/db/migrations/0004_add_documents.sql +2 -1
  40. package/src/lib/db/migrations/0005_add_document_preprocessing.sql +2 -0
  41. package/src/lib/db/migrations/0006_add_agent_profile.sql +1 -0
  42. package/src/lib/db/migrations/0007_add_usage_metering_ledger.sql +9 -2
  43. package/src/lib/db/migrations/meta/_journal.json +43 -1
  44. package/src/lib/db/schema.ts +34 -0
  45. package/src/lib/desktop/__tests__/sidecar-launch.test.ts +70 -0
  46. package/src/lib/desktop/sidecar-launch.ts +85 -0
  47. package/src/lib/documents/__tests__/context-builder.test.ts +57 -0
  48. package/src/lib/documents/__tests__/output-scanner.test.ts +141 -0
  49. package/src/lib/notifications/actionable.ts +21 -7
  50. package/src/lib/settings/__tests__/auth.test.ts +220 -0
  51. package/src/lib/settings/__tests__/budget-guardrails.test.ts +181 -0
  52. package/src/lib/tauri-bridge.ts +138 -0
  53. package/src/lib/usage/__tests__/ledger.test.ts +284 -0
  54. package/src/lib/utils/__tests__/crypto.test.ts +90 -0
  55. package/src/lib/validators/__tests__/profile.test.ts +119 -0
  56. package/src/lib/validators/__tests__/project.test.ts +82 -0
  57. package/src/lib/validators/__tests__/settings.test.ts +151 -0
  58. package/src/lib/validators/__tests__/task.test.ts +144 -0
  59. package/src/lib/workflows/__tests__/definition-validation.test.ts +164 -0
  60. package/src/lib/workflows/__tests__/engine.test.ts +114 -0
  61. package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
  62. package/src/lib/workflows/__tests__/parallel.test.ts +75 -0
  63. package/src/lib/workflows/__tests__/swarm.test.ts +97 -0
  64. package/src/test/setup.ts +10 -0
@@ -0,0 +1,56 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import Database from "better-sqlite3";
3
+ import { join } from "path";
4
+ import { mkdtempSync, rmSync } from "fs";
5
+ import { tmpdir } from "os";
6
+ import { drizzle } from "drizzle-orm/better-sqlite3";
7
+ import { migrate } from "drizzle-orm/better-sqlite3/migrator";
8
+ import {
9
+ bootstrapStagentDatabase,
10
+ hasLegacyStagentTables,
11
+ hasMigrationHistory,
12
+ markAllMigrationsApplied,
13
+ } from "../bootstrap";
14
+
15
+ const migrationsFolder = join(process.cwd(), "src", "lib", "db", "migrations");
16
+
17
+ describe("database bootstrap recovery", () => {
18
+ let tempDir: string;
19
+ let dbPath: string;
20
+
21
+ beforeEach(() => {
22
+ tempDir = mkdtempSync(join(tmpdir(), "stagent-db-bootstrap-"));
23
+ dbPath = join(tempDir, "stagent.db");
24
+ });
25
+
26
+ afterEach(() => {
27
+ rmSync(tempDir, { recursive: true, force: true });
28
+ });
29
+
30
+ it("recovers a bootstrapped database that has no drizzle migration history", () => {
31
+ const bootstrapDb = new Database(dbPath);
32
+ bootstrapStagentDatabase(bootstrapDb);
33
+
34
+ expect(hasLegacyStagentTables(bootstrapDb)).toBe(true);
35
+ expect(hasMigrationHistory(bootstrapDb)).toBe(false);
36
+
37
+ markAllMigrationsApplied(bootstrapDb, migrationsFolder);
38
+ expect(hasMigrationHistory(bootstrapDb)).toBe(true);
39
+ bootstrapDb.close();
40
+
41
+ const migratedDb = new Database(dbPath);
42
+ const drizzleDb = drizzle(migratedDb);
43
+
44
+ expect(() =>
45
+ migrate(drizzleDb, {
46
+ migrationsFolder,
47
+ })
48
+ ).not.toThrow();
49
+
50
+ const migrationCount = migratedDb
51
+ .prepare("SELECT COUNT(*) AS count FROM __drizzle_migrations")
52
+ .get() as { count: number };
53
+ expect(migrationCount.count).toBe(9);
54
+ migratedDb.close();
55
+ });
56
+ });
@@ -0,0 +1,301 @@
1
+ import type Database from "better-sqlite3";
2
+ import { readMigrationFiles } from "drizzle-orm/migrator";
3
+
4
+ const STAGENT_TABLES = [
5
+ "projects",
6
+ "tasks",
7
+ "workflows",
8
+ "agent_logs",
9
+ "notifications",
10
+ "settings",
11
+ "documents",
12
+ "schedules",
13
+ "usage_ledger",
14
+ "learned_context",
15
+ ] as const;
16
+
17
+ export function bootstrapStagentDatabase(sqlite: Database.Database): void {
18
+ sqlite.exec(`
19
+ CREATE TABLE IF NOT EXISTS projects (
20
+ id TEXT PRIMARY KEY NOT NULL,
21
+ name TEXT NOT NULL,
22
+ description TEXT,
23
+ status TEXT DEFAULT 'active' NOT NULL,
24
+ created_at INTEGER NOT NULL,
25
+ updated_at INTEGER NOT NULL
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS tasks (
29
+ id TEXT PRIMARY KEY NOT NULL,
30
+ project_id TEXT,
31
+ workflow_id TEXT,
32
+ schedule_id TEXT,
33
+ title TEXT NOT NULL,
34
+ description TEXT,
35
+ status TEXT DEFAULT 'planned' NOT NULL,
36
+ assigned_agent TEXT,
37
+ agent_profile TEXT,
38
+ priority INTEGER DEFAULT 2 NOT NULL,
39
+ result TEXT,
40
+ session_id TEXT,
41
+ resume_count INTEGER DEFAULT 0 NOT NULL,
42
+ created_at INTEGER NOT NULL,
43
+ updated_at INTEGER NOT NULL,
44
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
45
+ FOREIGN KEY (workflow_id) REFERENCES workflows(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
46
+ FOREIGN KEY (schedule_id) REFERENCES schedules(id) ON UPDATE NO ACTION ON DELETE NO ACTION
47
+ );
48
+
49
+ CREATE TABLE IF NOT EXISTS workflows (
50
+ id TEXT PRIMARY KEY NOT NULL,
51
+ project_id TEXT,
52
+ name TEXT NOT NULL,
53
+ definition TEXT NOT NULL,
54
+ status TEXT DEFAULT 'draft' NOT NULL,
55
+ created_at INTEGER NOT NULL,
56
+ updated_at INTEGER NOT NULL,
57
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON UPDATE NO ACTION ON DELETE NO ACTION
58
+ );
59
+
60
+ CREATE TABLE IF NOT EXISTS agent_logs (
61
+ id TEXT PRIMARY KEY NOT NULL,
62
+ task_id TEXT,
63
+ agent_type TEXT NOT NULL,
64
+ event TEXT NOT NULL,
65
+ payload TEXT,
66
+ timestamp INTEGER NOT NULL,
67
+ FOREIGN KEY (task_id) REFERENCES tasks(id) ON UPDATE NO ACTION ON DELETE NO ACTION
68
+ );
69
+
70
+ CREATE TABLE IF NOT EXISTS notifications (
71
+ id TEXT PRIMARY KEY NOT NULL,
72
+ task_id TEXT,
73
+ type TEXT NOT NULL,
74
+ title TEXT NOT NULL,
75
+ body TEXT,
76
+ read INTEGER DEFAULT 0 NOT NULL,
77
+ tool_name TEXT,
78
+ tool_input TEXT,
79
+ response TEXT,
80
+ responded_at INTEGER,
81
+ created_at INTEGER NOT NULL,
82
+ FOREIGN KEY (task_id) REFERENCES tasks(id) ON UPDATE NO ACTION ON DELETE NO ACTION
83
+ );
84
+
85
+ CREATE TABLE IF NOT EXISTS settings (
86
+ key TEXT PRIMARY KEY,
87
+ value TEXT NOT NULL,
88
+ updated_at INTEGER NOT NULL
89
+ );
90
+
91
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
92
+ CREATE INDEX IF NOT EXISTS idx_tasks_project_id ON tasks(project_id);
93
+ CREATE INDEX IF NOT EXISTS idx_agent_logs_task_id ON agent_logs(task_id);
94
+ CREATE INDEX IF NOT EXISTS idx_agent_logs_timestamp ON agent_logs(timestamp);
95
+
96
+ CREATE TABLE IF NOT EXISTS documents (
97
+ id TEXT PRIMARY KEY NOT NULL,
98
+ task_id TEXT,
99
+ project_id TEXT,
100
+ filename TEXT NOT NULL,
101
+ original_name TEXT NOT NULL,
102
+ mime_type TEXT NOT NULL,
103
+ size INTEGER NOT NULL,
104
+ storage_path TEXT NOT NULL,
105
+ version INTEGER DEFAULT 1 NOT NULL,
106
+ direction TEXT DEFAULT 'input' NOT NULL,
107
+ category TEXT,
108
+ status TEXT DEFAULT 'uploaded' NOT NULL,
109
+ extracted_text TEXT,
110
+ processed_path TEXT,
111
+ processing_error TEXT,
112
+ created_at INTEGER NOT NULL,
113
+ updated_at INTEGER NOT NULL,
114
+ FOREIGN KEY (task_id) REFERENCES tasks(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
115
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON UPDATE NO ACTION ON DELETE NO ACTION
116
+ );
117
+
118
+ CREATE TABLE IF NOT EXISTS schedules (
119
+ id TEXT PRIMARY KEY NOT NULL,
120
+ project_id TEXT,
121
+ name TEXT NOT NULL,
122
+ prompt TEXT NOT NULL,
123
+ cron_expression TEXT NOT NULL,
124
+ assigned_agent TEXT,
125
+ agent_profile TEXT,
126
+ recurs INTEGER DEFAULT 1 NOT NULL,
127
+ status TEXT DEFAULT 'active' NOT NULL,
128
+ max_firings INTEGER,
129
+ firing_count INTEGER DEFAULT 0 NOT NULL,
130
+ expires_at INTEGER,
131
+ last_fired_at INTEGER,
132
+ next_fire_at INTEGER,
133
+ created_at INTEGER NOT NULL,
134
+ updated_at INTEGER NOT NULL,
135
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON UPDATE NO ACTION ON DELETE NO ACTION
136
+ );
137
+
138
+ CREATE INDEX IF NOT EXISTS idx_schedules_status ON schedules(status);
139
+ CREATE INDEX IF NOT EXISTS idx_schedules_next_fire_at ON schedules(next_fire_at);
140
+ CREATE INDEX IF NOT EXISTS idx_schedules_project_id ON schedules(project_id);
141
+
142
+ CREATE INDEX IF NOT EXISTS idx_notifications_task_id ON notifications(task_id);
143
+ CREATE INDEX IF NOT EXISTS idx_notifications_read ON notifications(read);
144
+ CREATE INDEX IF NOT EXISTS idx_documents_task_id ON documents(task_id);
145
+ CREATE INDEX IF NOT EXISTS idx_documents_project_id ON documents(project_id);
146
+
147
+ CREATE TABLE IF NOT EXISTS usage_ledger (
148
+ id TEXT PRIMARY KEY NOT NULL,
149
+ task_id TEXT,
150
+ workflow_id TEXT,
151
+ schedule_id TEXT,
152
+ project_id TEXT,
153
+ activity_type TEXT NOT NULL,
154
+ runtime_id TEXT NOT NULL,
155
+ provider_id TEXT NOT NULL,
156
+ model_id TEXT,
157
+ status TEXT NOT NULL,
158
+ input_tokens INTEGER,
159
+ output_tokens INTEGER,
160
+ total_tokens INTEGER,
161
+ cost_micros INTEGER,
162
+ pricing_version TEXT,
163
+ started_at INTEGER NOT NULL,
164
+ finished_at INTEGER NOT NULL,
165
+ FOREIGN KEY (task_id) REFERENCES tasks(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
166
+ FOREIGN KEY (workflow_id) REFERENCES workflows(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
167
+ FOREIGN KEY (schedule_id) REFERENCES schedules(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
168
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON UPDATE NO ACTION ON DELETE NO ACTION
169
+ );
170
+
171
+ CREATE INDEX IF NOT EXISTS idx_usage_ledger_task_id ON usage_ledger(task_id);
172
+ CREATE INDEX IF NOT EXISTS idx_usage_ledger_activity_type ON usage_ledger(activity_type);
173
+ CREATE INDEX IF NOT EXISTS idx_usage_ledger_runtime_id ON usage_ledger(runtime_id);
174
+ CREATE INDEX IF NOT EXISTS idx_usage_ledger_provider_model ON usage_ledger(provider_id, model_id);
175
+ CREATE INDEX IF NOT EXISTS idx_usage_ledger_finished_at ON usage_ledger(finished_at);
176
+
177
+ CREATE TABLE IF NOT EXISTS learned_context (
178
+ id TEXT PRIMARY KEY NOT NULL,
179
+ profile_id TEXT NOT NULL,
180
+ version INTEGER NOT NULL,
181
+ content TEXT,
182
+ diff TEXT,
183
+ change_type TEXT NOT NULL,
184
+ source_task_id TEXT,
185
+ proposal_notification_id TEXT,
186
+ proposed_additions TEXT,
187
+ approved_by TEXT,
188
+ created_at INTEGER NOT NULL,
189
+ FOREIGN KEY (source_task_id) REFERENCES tasks(id) ON UPDATE NO ACTION ON DELETE NO ACTION
190
+ );
191
+
192
+ CREATE INDEX IF NOT EXISTS idx_learned_context_profile_version ON learned_context(profile_id, version);
193
+ CREATE INDEX IF NOT EXISTS idx_learned_context_change_type ON learned_context(change_type);
194
+ `);
195
+
196
+ try {
197
+ sqlite.exec(`ALTER TABLE tasks ADD COLUMN agent_profile TEXT;`);
198
+ } catch {
199
+ // Column already exists.
200
+ }
201
+ sqlite.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_agent_profile ON tasks(agent_profile);`);
202
+
203
+ try {
204
+ sqlite.exec(`ALTER TABLE tasks ADD COLUMN workflow_id TEXT REFERENCES workflows(id);`);
205
+ } catch {
206
+ // Column already exists.
207
+ }
208
+ sqlite.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_workflow_id ON tasks(workflow_id);`);
209
+
210
+ try {
211
+ sqlite.exec(`ALTER TABLE tasks ADD COLUMN schedule_id TEXT REFERENCES schedules(id);`);
212
+ } catch {
213
+ // Column already exists.
214
+ }
215
+ sqlite.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_schedule_id ON tasks(schedule_id);`);
216
+
217
+ try {
218
+ sqlite.exec(`ALTER TABLE projects ADD COLUMN working_directory TEXT;`);
219
+ } catch {
220
+ // Column already exists.
221
+ }
222
+
223
+ try {
224
+ sqlite.exec(`ALTER TABLE schedules ADD COLUMN assigned_agent TEXT;`);
225
+ } catch {
226
+ // Column already exists.
227
+ }
228
+
229
+ try {
230
+ sqlite.exec(`ALTER TABLE documents ADD COLUMN version INTEGER NOT NULL DEFAULT 1;`);
231
+ } catch {
232
+ // Column already exists.
233
+ }
234
+ }
235
+
236
+ export function hasLegacyStagentTables(sqlite: Database.Database): boolean {
237
+ const placeholders = STAGENT_TABLES.map(() => "?").join(", ");
238
+ const row = sqlite
239
+ .prepare(
240
+ `SELECT COUNT(*) AS count
241
+ FROM sqlite_master
242
+ WHERE type = 'table' AND name IN (${placeholders})`
243
+ )
244
+ .get(...STAGENT_TABLES) as { count: number };
245
+
246
+ return row.count > 0;
247
+ }
248
+
249
+ export function hasMigrationHistory(
250
+ sqlite: Database.Database,
251
+ migrationsTable = "__drizzle_migrations"
252
+ ): boolean {
253
+ const tableRow = sqlite
254
+ .prepare(
255
+ `SELECT COUNT(*) AS count
256
+ FROM sqlite_master
257
+ WHERE type = 'table' AND name = ?`
258
+ )
259
+ .get(migrationsTable) as { count: number };
260
+
261
+ if (tableRow.count === 0) {
262
+ return false;
263
+ }
264
+
265
+ const row = sqlite
266
+ .prepare(`SELECT COUNT(*) AS count FROM ${migrationsTable}`)
267
+ .get() as { count: number };
268
+
269
+ return row.count > 0;
270
+ }
271
+
272
+ export function markAllMigrationsApplied(
273
+ sqlite: Database.Database,
274
+ migrationsFolder: string,
275
+ migrationsTable = "__drizzle_migrations"
276
+ ): void {
277
+ const migrations = readMigrationFiles({ migrationsFolder, migrationsTable });
278
+
279
+ sqlite.exec(`
280
+ CREATE TABLE IF NOT EXISTS ${migrationsTable} (
281
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
282
+ hash TEXT NOT NULL,
283
+ created_at NUMERIC
284
+ )
285
+ `);
286
+
287
+ const existing = sqlite
288
+ .prepare(`SELECT hash, created_at FROM ${migrationsTable}`)
289
+ .all() as Array<{ hash: string; created_at: number }>;
290
+ const seen = new Set(existing.map((row) => `${row.hash}:${row.created_at}`));
291
+ const insert = sqlite.prepare(
292
+ `INSERT INTO ${migrationsTable} (hash, created_at) VALUES (?, ?)`
293
+ );
294
+
295
+ for (const migration of migrations) {
296
+ const key = `${migration.hash}:${migration.folderMillis}`;
297
+ if (!seen.has(key)) {
298
+ insert.run(migration.hash, migration.folderMillis);
299
+ }
300
+ }
301
+ }
@@ -4,6 +4,7 @@ import * as schema from "./schema";
4
4
  import { join } from "path";
5
5
  import { mkdirSync } from "fs";
6
6
  import { getStagentDataDir } from "@/lib/utils/stagent-paths";
7
+ import { bootstrapStagentDatabase } from "./bootstrap";
7
8
 
8
9
  const dataDir = getStagentDataDir();
9
10
  mkdirSync(dataDir, { recursive: true });
@@ -12,210 +13,6 @@ const dbPath = join(dataDir, "stagent.db");
12
13
  const sqlite = new Database(dbPath);
13
14
  sqlite.pragma("journal_mode = WAL");
14
15
  sqlite.pragma("foreign_keys = ON");
15
-
16
- // Bootstrap all tables (migrations may not have been applied on fresh install)
17
- // Note: sqlite.exec() here is better-sqlite3's synchronous DDL method, not child_process
18
- sqlite.exec(`
19
- CREATE TABLE IF NOT EXISTS projects (
20
- id TEXT PRIMARY KEY NOT NULL,
21
- name TEXT NOT NULL,
22
- description TEXT,
23
- status TEXT DEFAULT 'active' NOT NULL,
24
- created_at INTEGER NOT NULL,
25
- updated_at INTEGER NOT NULL
26
- );
27
-
28
- CREATE TABLE IF NOT EXISTS tasks (
29
- id TEXT PRIMARY KEY NOT NULL,
30
- project_id TEXT,
31
- workflow_id TEXT,
32
- schedule_id TEXT,
33
- title TEXT NOT NULL,
34
- description TEXT,
35
- status TEXT DEFAULT 'planned' NOT NULL,
36
- assigned_agent TEXT,
37
- agent_profile TEXT,
38
- priority INTEGER DEFAULT 2 NOT NULL,
39
- result TEXT,
40
- session_id TEXT,
41
- resume_count INTEGER DEFAULT 0 NOT NULL,
42
- created_at INTEGER NOT NULL,
43
- updated_at INTEGER NOT NULL,
44
- FOREIGN KEY (project_id) REFERENCES projects(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
45
- FOREIGN KEY (workflow_id) REFERENCES workflows(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
46
- FOREIGN KEY (schedule_id) REFERENCES schedules(id) ON UPDATE NO ACTION ON DELETE NO ACTION
47
- );
48
-
49
- CREATE TABLE IF NOT EXISTS workflows (
50
- id TEXT PRIMARY KEY NOT NULL,
51
- project_id TEXT,
52
- name TEXT NOT NULL,
53
- definition TEXT NOT NULL,
54
- status TEXT DEFAULT 'draft' NOT NULL,
55
- created_at INTEGER NOT NULL,
56
- updated_at INTEGER NOT NULL,
57
- FOREIGN KEY (project_id) REFERENCES projects(id) ON UPDATE NO ACTION ON DELETE NO ACTION
58
- );
59
-
60
- CREATE TABLE IF NOT EXISTS agent_logs (
61
- id TEXT PRIMARY KEY NOT NULL,
62
- task_id TEXT,
63
- agent_type TEXT NOT NULL,
64
- event TEXT NOT NULL,
65
- payload TEXT,
66
- timestamp INTEGER NOT NULL,
67
- FOREIGN KEY (task_id) REFERENCES tasks(id) ON UPDATE NO ACTION ON DELETE NO ACTION
68
- );
69
-
70
- CREATE TABLE IF NOT EXISTS notifications (
71
- id TEXT PRIMARY KEY NOT NULL,
72
- task_id TEXT,
73
- type TEXT NOT NULL,
74
- title TEXT NOT NULL,
75
- body TEXT,
76
- read INTEGER DEFAULT 0 NOT NULL,
77
- tool_name TEXT,
78
- tool_input TEXT,
79
- response TEXT,
80
- responded_at INTEGER,
81
- created_at INTEGER NOT NULL,
82
- FOREIGN KEY (task_id) REFERENCES tasks(id) ON UPDATE NO ACTION ON DELETE NO ACTION
83
- );
84
-
85
- CREATE TABLE IF NOT EXISTS settings (
86
- key TEXT PRIMARY KEY,
87
- value TEXT NOT NULL,
88
- updated_at INTEGER NOT NULL
89
- );
90
-
91
- CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
92
- CREATE INDEX IF NOT EXISTS idx_tasks_project_id ON tasks(project_id);
93
- CREATE INDEX IF NOT EXISTS idx_agent_logs_task_id ON agent_logs(task_id);
94
- CREATE INDEX IF NOT EXISTS idx_agent_logs_timestamp ON agent_logs(timestamp);
95
- CREATE TABLE IF NOT EXISTS documents (
96
- id TEXT PRIMARY KEY NOT NULL,
97
- task_id TEXT,
98
- project_id TEXT,
99
- filename TEXT NOT NULL,
100
- original_name TEXT NOT NULL,
101
- mime_type TEXT NOT NULL,
102
- size INTEGER NOT NULL,
103
- storage_path TEXT NOT NULL,
104
- version INTEGER DEFAULT 1 NOT NULL,
105
- direction TEXT DEFAULT 'input' NOT NULL,
106
- category TEXT,
107
- status TEXT DEFAULT 'uploaded' NOT NULL,
108
- extracted_text TEXT,
109
- processed_path TEXT,
110
- processing_error TEXT,
111
- created_at INTEGER NOT NULL,
112
- updated_at INTEGER NOT NULL,
113
- FOREIGN KEY (task_id) REFERENCES tasks(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
114
- FOREIGN KEY (project_id) REFERENCES projects(id) ON UPDATE NO ACTION ON DELETE NO ACTION
115
- );
116
-
117
- CREATE TABLE IF NOT EXISTS schedules (
118
- id TEXT PRIMARY KEY NOT NULL,
119
- project_id TEXT,
120
- name TEXT NOT NULL,
121
- prompt TEXT NOT NULL,
122
- cron_expression TEXT NOT NULL,
123
- assigned_agent TEXT,
124
- agent_profile TEXT,
125
- recurs INTEGER DEFAULT 1 NOT NULL,
126
- status TEXT DEFAULT 'active' NOT NULL,
127
- max_firings INTEGER,
128
- firing_count INTEGER DEFAULT 0 NOT NULL,
129
- expires_at INTEGER,
130
- last_fired_at INTEGER,
131
- next_fire_at INTEGER,
132
- created_at INTEGER NOT NULL,
133
- updated_at INTEGER NOT NULL,
134
- FOREIGN KEY (project_id) REFERENCES projects(id) ON UPDATE NO ACTION ON DELETE NO ACTION
135
- );
136
-
137
- CREATE INDEX IF NOT EXISTS idx_schedules_status ON schedules(status);
138
- CREATE INDEX IF NOT EXISTS idx_schedules_next_fire_at ON schedules(next_fire_at);
139
- CREATE INDEX IF NOT EXISTS idx_schedules_project_id ON schedules(project_id);
140
-
141
- CREATE INDEX IF NOT EXISTS idx_notifications_task_id ON notifications(task_id);
142
- CREATE INDEX IF NOT EXISTS idx_notifications_read ON notifications(read);
143
- CREATE INDEX IF NOT EXISTS idx_documents_task_id ON documents(task_id);
144
- CREATE INDEX IF NOT EXISTS idx_documents_project_id ON documents(project_id);
145
-
146
- CREATE TABLE IF NOT EXISTS usage_ledger (
147
- id TEXT PRIMARY KEY NOT NULL,
148
- task_id TEXT,
149
- workflow_id TEXT,
150
- schedule_id TEXT,
151
- project_id TEXT,
152
- activity_type TEXT NOT NULL,
153
- runtime_id TEXT NOT NULL,
154
- provider_id TEXT NOT NULL,
155
- model_id TEXT,
156
- status TEXT NOT NULL,
157
- input_tokens INTEGER,
158
- output_tokens INTEGER,
159
- total_tokens INTEGER,
160
- cost_micros INTEGER,
161
- pricing_version TEXT,
162
- started_at INTEGER NOT NULL,
163
- finished_at INTEGER NOT NULL,
164
- FOREIGN KEY (task_id) REFERENCES tasks(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
165
- FOREIGN KEY (workflow_id) REFERENCES workflows(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
166
- FOREIGN KEY (schedule_id) REFERENCES schedules(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
167
- FOREIGN KEY (project_id) REFERENCES projects(id) ON UPDATE NO ACTION ON DELETE NO ACTION
168
- );
169
-
170
- CREATE INDEX IF NOT EXISTS idx_usage_ledger_task_id ON usage_ledger(task_id);
171
- CREATE INDEX IF NOT EXISTS idx_usage_ledger_activity_type ON usage_ledger(activity_type);
172
- CREATE INDEX IF NOT EXISTS idx_usage_ledger_runtime_id ON usage_ledger(runtime_id);
173
- CREATE INDEX IF NOT EXISTS idx_usage_ledger_provider_model ON usage_ledger(provider_id, model_id);
174
- CREATE INDEX IF NOT EXISTS idx_usage_ledger_finished_at ON usage_ledger(finished_at);
175
- `);
176
-
177
- // Migration: add agent_profile column to existing tasks table (safe to re-run)
178
- // Note: sqlite.exec() here is better-sqlite3's synchronous DDL method, not child_process
179
- try {
180
- sqlite.exec(`ALTER TABLE tasks ADD COLUMN agent_profile TEXT;`);
181
- } catch {
182
- // Column already exists — ignore
183
- }
184
- sqlite.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_agent_profile ON tasks(agent_profile);`);
185
-
186
- try {
187
- sqlite.exec(`ALTER TABLE tasks ADD COLUMN workflow_id TEXT REFERENCES workflows(id);`);
188
- } catch {
189
- // Column already exists — ignore
190
- }
191
- sqlite.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_workflow_id ON tasks(workflow_id);`);
192
-
193
- try {
194
- sqlite.exec(`ALTER TABLE tasks ADD COLUMN schedule_id TEXT REFERENCES schedules(id);`);
195
- } catch {
196
- // Column already exists — ignore
197
- }
198
- sqlite.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_schedule_id ON tasks(schedule_id);`);
199
-
200
- // Migration: add working_directory column to existing projects table (safe to re-run)
201
- // Note: sqlite.exec() here is better-sqlite3's synchronous DDL method, not child_process
202
- try {
203
- sqlite.exec(`ALTER TABLE projects ADD COLUMN working_directory TEXT;`);
204
- } catch {
205
- // Column already exists — ignore
206
- }
207
-
208
- // Migration: add assigned_agent column to existing schedules table (safe to re-run)
209
- try {
210
- sqlite.exec(`ALTER TABLE schedules ADD COLUMN assigned_agent TEXT;`);
211
- } catch {
212
- // Column already exists — ignore
213
- }
214
-
215
- try {
216
- sqlite.exec(`ALTER TABLE documents ADD COLUMN version INTEGER NOT NULL DEFAULT 1;`);
217
- } catch {
218
- // Column already exists — ignore
219
- }
16
+ bootstrapStagentDatabase(sqlite);
220
17
 
221
18
  export const db = drizzle(sqlite, { schema });
@@ -15,6 +15,7 @@ CREATE TABLE IF NOT EXISTS documents (
15
15
  FOREIGN KEY (task_id) REFERENCES tasks(id) ON UPDATE NO ACTION ON DELETE NO ACTION,
16
16
  FOREIGN KEY (project_id) REFERENCES projects(id) ON UPDATE NO ACTION ON DELETE NO ACTION
17
17
  );
18
-
18
+ --> statement-breakpoint
19
19
  CREATE INDEX IF NOT EXISTS idx_documents_task_id ON documents(task_id);
20
+ --> statement-breakpoint
20
21
  CREATE INDEX IF NOT EXISTS idx_documents_project_id ON documents(project_id);
@@ -1,4 +1,6 @@
1
1
  -- Add preprocessing columns to documents table
2
2
  ALTER TABLE documents ADD COLUMN extracted_text TEXT;
3
+ --> statement-breakpoint
3
4
  ALTER TABLE documents ADD COLUMN processed_path TEXT;
5
+ --> statement-breakpoint
4
6
  ALTER TABLE documents ADD COLUMN processing_error TEXT;
@@ -1,2 +1,3 @@
1
1
  ALTER TABLE tasks ADD COLUMN agent_profile TEXT;
2
+ --> statement-breakpoint
2
3
  CREATE INDEX IF NOT EXISTS idx_tasks_agent_profile ON tasks(agent_profile);
@@ -1,8 +1,11 @@
1
1
  ALTER TABLE tasks ADD COLUMN workflow_id TEXT REFERENCES workflows(id);
2
+ --> statement-breakpoint
2
3
  ALTER TABLE tasks ADD COLUMN schedule_id TEXT REFERENCES schedules(id);
4
+ --> statement-breakpoint
3
5
  CREATE INDEX IF NOT EXISTS idx_tasks_workflow_id ON tasks(workflow_id);
6
+ --> statement-breakpoint
4
7
  CREATE INDEX IF NOT EXISTS idx_tasks_schedule_id ON tasks(schedule_id);
5
-
8
+ --> statement-breakpoint
6
9
  CREATE TABLE IF NOT EXISTS usage_ledger (
7
10
  id TEXT PRIMARY KEY NOT NULL,
8
11
  task_id TEXT REFERENCES tasks(id),
@@ -22,9 +25,13 @@ CREATE TABLE IF NOT EXISTS usage_ledger (
22
25
  started_at INTEGER NOT NULL,
23
26
  finished_at INTEGER NOT NULL
24
27
  );
25
-
28
+ --> statement-breakpoint
26
29
  CREATE INDEX IF NOT EXISTS idx_usage_ledger_task_id ON usage_ledger(task_id);
30
+ --> statement-breakpoint
27
31
  CREATE INDEX IF NOT EXISTS idx_usage_ledger_activity_type ON usage_ledger(activity_type);
32
+ --> statement-breakpoint
28
33
  CREATE INDEX IF NOT EXISTS idx_usage_ledger_runtime_id ON usage_ledger(runtime_id);
34
+ --> statement-breakpoint
29
35
  CREATE INDEX IF NOT EXISTS idx_usage_ledger_provider_model ON usage_ledger(provider_id, model_id);
36
+ --> statement-breakpoint
30
37
  CREATE INDEX IF NOT EXISTS idx_usage_ledger_finished_at ON usage_ledger(finished_at);
@@ -22,6 +22,48 @@
22
22
  "when": 1773187200000,
23
23
  "tag": "0002_add_resume_count",
24
24
  "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "6",
29
+ "when": 1773273600000,
30
+ "tag": "0003_add_settings",
31
+ "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "6",
36
+ "when": 1773360000000,
37
+ "tag": "0004_add_documents",
38
+ "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "6",
43
+ "when": 1773446400000,
44
+ "tag": "0005_add_document_preprocessing",
45
+ "breakpoints": true
46
+ },
47
+ {
48
+ "idx": 6,
49
+ "version": "6",
50
+ "when": 1773532800000,
51
+ "tag": "0006_add_agent_profile",
52
+ "breakpoints": true
53
+ },
54
+ {
55
+ "idx": 7,
56
+ "version": "6",
57
+ "when": 1773619200000,
58
+ "tag": "0007_add_usage_metering_ledger",
59
+ "breakpoints": true
60
+ },
61
+ {
62
+ "idx": 8,
63
+ "version": "6",
64
+ "when": 1773705600000,
65
+ "tag": "0008_add_document_version",
66
+ "breakpoints": true
25
67
  }
26
68
  ]
27
- }
69
+ }