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.
- package/README.md +33 -30
- package/dist/cli.js +376 -49
- package/package.json +23 -24
- package/public/desktop-icon-512.png +0 -0
- package/public/icon-512.png +0 -0
- package/src/app/api/data/clear/route.ts +0 -7
- package/src/app/api/data/seed/route.ts +0 -7
- package/src/app/api/profiles/[id]/context/route.ts +109 -0
- package/src/components/dashboard/__tests__/accessibility.test.tsx +42 -0
- package/src/components/documents/__tests__/document-upload-dialog.test.tsx +46 -0
- package/src/components/notifications/__tests__/pending-approval-host.test.tsx +122 -0
- package/src/components/notifications/__tests__/permission-response-actions.test.tsx +79 -0
- package/src/components/notifications/pending-approval-host.tsx +49 -25
- package/src/components/profiles/context-proposal-review.tsx +145 -0
- package/src/components/profiles/learned-context-panel.tsx +286 -0
- package/src/components/profiles/profile-detail-view.tsx +4 -0
- package/src/components/projects/__tests__/dialog-focus.test.tsx +87 -0
- package/src/components/tasks/__tests__/kanban-board-accessibility.test.tsx +59 -0
- package/src/lib/__tests__/setup-verify.test.ts +28 -0
- package/src/lib/__tests__/utils.test.ts +29 -0
- package/src/lib/agents/__tests__/claude-agent.test.ts +946 -0
- package/src/lib/agents/__tests__/execution-manager.test.ts +63 -0
- package/src/lib/agents/__tests__/router.test.ts +61 -0
- package/src/lib/agents/claude-agent.ts +34 -5
- package/src/lib/agents/learned-context.ts +322 -0
- package/src/lib/agents/pattern-extractor.ts +150 -0
- package/src/lib/agents/profiles/__tests__/compatibility.test.ts +76 -0
- package/src/lib/agents/profiles/__tests__/registry.test.ts +177 -0
- package/src/lib/agents/profiles/builtins/sweep/SKILL.md +47 -0
- package/src/lib/agents/profiles/builtins/sweep/profile.yaml +12 -0
- package/src/lib/agents/runtime/__tests__/catalog.test.ts +38 -0
- package/src/lib/agents/runtime/openai-codex.ts +1 -1
- package/src/lib/agents/sweep.ts +65 -0
- package/src/lib/constants/__tests__/task-status.test.ts +119 -0
- package/src/lib/data/seed-data/__tests__/profiles.test.ts +141 -0
- package/src/lib/db/__tests__/bootstrap.test.ts +56 -0
- package/src/lib/db/bootstrap.ts +301 -0
- package/src/lib/db/index.ts +2 -205
- package/src/lib/db/migrations/0004_add_documents.sql +2 -1
- package/src/lib/db/migrations/0005_add_document_preprocessing.sql +2 -0
- package/src/lib/db/migrations/0006_add_agent_profile.sql +1 -0
- package/src/lib/db/migrations/0007_add_usage_metering_ledger.sql +9 -2
- package/src/lib/db/migrations/meta/_journal.json +43 -1
- package/src/lib/db/schema.ts +34 -0
- package/src/lib/desktop/__tests__/sidecar-launch.test.ts +70 -0
- package/src/lib/desktop/sidecar-launch.ts +85 -0
- package/src/lib/documents/__tests__/context-builder.test.ts +57 -0
- package/src/lib/documents/__tests__/output-scanner.test.ts +141 -0
- package/src/lib/notifications/actionable.ts +21 -7
- package/src/lib/settings/__tests__/auth.test.ts +220 -0
- package/src/lib/settings/__tests__/budget-guardrails.test.ts +181 -0
- package/src/lib/tauri-bridge.ts +138 -0
- package/src/lib/usage/__tests__/ledger.test.ts +284 -0
- package/src/lib/utils/__tests__/crypto.test.ts +90 -0
- package/src/lib/validators/__tests__/profile.test.ts +119 -0
- package/src/lib/validators/__tests__/project.test.ts +82 -0
- package/src/lib/validators/__tests__/settings.test.ts +151 -0
- package/src/lib/validators/__tests__/task.test.ts +144 -0
- package/src/lib/workflows/__tests__/definition-validation.test.ts +164 -0
- package/src/lib/workflows/__tests__/engine.test.ts +114 -0
- package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
- package/src/lib/workflows/__tests__/parallel.test.ts +75 -0
- package/src/lib/workflows/__tests__/swarm.test.ts +97 -0
- 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
|
+
}
|
package/src/lib/db/index.ts
CHANGED
|
@@ -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,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
|
+
}
|