bopodev-db 0.1.27 → 0.1.29
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/bootstrap.d.ts +3 -3
- package/dist/client.d.ts +34 -6
- package/dist/index.d.ts +2 -0
- package/dist/migrate.d.ts +1 -0
- package/dist/ping.d.ts +3 -0
- package/dist/repositories/companies.d.ts +32 -0
- package/dist/repositories/helpers.d.ts +16 -0
- package/dist/repositories/index.d.ts +3 -0
- package/dist/repositories/legacy.d.ts +1415 -0
- package/dist/repositories.d.ts +3 -36
- package/dist/schema.d.ts +218 -0
- package/drizzle.config.ts +10 -0
- package/package.json +12 -3
- package/src/bootstrap.ts +9 -595
- package/src/client.ts +634 -9
- package/src/default-paths.ts +1 -1
- package/src/index.ts +2 -0
- package/src/migrate.ts +20 -0
- package/src/migrations/0000_initial.sql +389 -0
- package/src/migrations/0001_issues_external_link.sql +1 -0
- package/src/migrations/0002_issues_goal_goals_owner_agent.sql +2 -0
- package/src/migrations/0003_issue_goals_junction.sql +12 -0
- package/src/migrations/meta/_journal.json +34 -0
- package/src/ping.ts +7 -0
- package/src/repositories/companies.ts +41 -0
- package/src/repositories/helpers.ts +104 -0
- package/src/repositories/index.ts +3 -0
- package/src/{repositories.ts → repositories/legacy.ts} +150 -123
- package/src/schema.ts +22 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
CREATE TABLE "companies" (
|
|
2
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
3
|
+
"name" text NOT NULL,
|
|
4
|
+
"mission" text,
|
|
5
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
6
|
+
);
|
|
7
|
+
--> statement-breakpoint
|
|
8
|
+
CREATE TABLE "projects" (
|
|
9
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
10
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
11
|
+
"name" text NOT NULL,
|
|
12
|
+
"description" text,
|
|
13
|
+
"status" text DEFAULT 'planned' NOT NULL,
|
|
14
|
+
"planned_start_at" timestamp,
|
|
15
|
+
"monthly_budget_usd" numeric(12, 4) DEFAULT 100 NOT NULL,
|
|
16
|
+
"used_budget_usd" numeric(12, 4) DEFAULT 0 NOT NULL,
|
|
17
|
+
"budget_window_start_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
18
|
+
"execution_workspace_policy" text,
|
|
19
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
20
|
+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
21
|
+
);
|
|
22
|
+
--> statement-breakpoint
|
|
23
|
+
CREATE TABLE "project_workspaces" (
|
|
24
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
25
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
26
|
+
"project_id" text NOT NULL REFERENCES "projects"("id") ON DELETE CASCADE,
|
|
27
|
+
"name" text NOT NULL,
|
|
28
|
+
"cwd" text,
|
|
29
|
+
"repo_url" text,
|
|
30
|
+
"repo_ref" text,
|
|
31
|
+
"is_primary" boolean DEFAULT false NOT NULL,
|
|
32
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
33
|
+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
34
|
+
);
|
|
35
|
+
--> statement-breakpoint
|
|
36
|
+
CREATE TABLE "goals" (
|
|
37
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
38
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
39
|
+
"project_id" text REFERENCES "projects"("id") ON DELETE SET NULL,
|
|
40
|
+
"parent_goal_id" text,
|
|
41
|
+
"level" text NOT NULL,
|
|
42
|
+
"title" text NOT NULL,
|
|
43
|
+
"description" text,
|
|
44
|
+
"status" text DEFAULT 'draft' NOT NULL,
|
|
45
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
46
|
+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
47
|
+
);
|
|
48
|
+
--> statement-breakpoint
|
|
49
|
+
CREATE TABLE "agents" (
|
|
50
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
51
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
52
|
+
"manager_agent_id" text,
|
|
53
|
+
"role" text NOT NULL,
|
|
54
|
+
"role_key" text,
|
|
55
|
+
"title" text,
|
|
56
|
+
"name" text NOT NULL,
|
|
57
|
+
"provider_type" text NOT NULL,
|
|
58
|
+
"status" text DEFAULT 'idle' NOT NULL,
|
|
59
|
+
"heartbeat_cron" text NOT NULL,
|
|
60
|
+
"monthly_budget_usd" numeric(12, 4) DEFAULT 0 NOT NULL,
|
|
61
|
+
"used_budget_usd" numeric(12, 4) DEFAULT 0 NOT NULL,
|
|
62
|
+
"token_usage" integer DEFAULT 0 NOT NULL,
|
|
63
|
+
"can_hire_agents" boolean DEFAULT false NOT NULL,
|
|
64
|
+
"avatar_seed" text DEFAULT '' NOT NULL,
|
|
65
|
+
"runtime_command" text,
|
|
66
|
+
"runtime_args_json" text DEFAULT '[]' NOT NULL,
|
|
67
|
+
"runtime_cwd" text,
|
|
68
|
+
"runtime_env_json" text DEFAULT '{}' NOT NULL,
|
|
69
|
+
"runtime_model" text,
|
|
70
|
+
"runtime_thinking_effort" text DEFAULT 'auto' NOT NULL,
|
|
71
|
+
"bootstrap_prompt" text,
|
|
72
|
+
"runtime_timeout_sec" integer DEFAULT 0 NOT NULL,
|
|
73
|
+
"interrupt_grace_sec" integer DEFAULT 15 NOT NULL,
|
|
74
|
+
"run_policy_json" text DEFAULT '{}' NOT NULL,
|
|
75
|
+
"state_blob" text DEFAULT '{}' NOT NULL,
|
|
76
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
77
|
+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
78
|
+
);
|
|
79
|
+
--> statement-breakpoint
|
|
80
|
+
CREATE TABLE "issues" (
|
|
81
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
82
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
83
|
+
"project_id" text NOT NULL REFERENCES "projects"("id") ON DELETE CASCADE,
|
|
84
|
+
"parent_issue_id" text,
|
|
85
|
+
"title" text NOT NULL,
|
|
86
|
+
"body" text,
|
|
87
|
+
"status" text DEFAULT 'todo' NOT NULL,
|
|
88
|
+
"priority" text DEFAULT 'none' NOT NULL,
|
|
89
|
+
"assignee_agent_id" text,
|
|
90
|
+
"labels_json" text DEFAULT '[]' NOT NULL,
|
|
91
|
+
"tags_json" text DEFAULT '[]' NOT NULL,
|
|
92
|
+
"is_claimed" boolean DEFAULT false NOT NULL,
|
|
93
|
+
"claimed_by_heartbeat_run_id" text,
|
|
94
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
95
|
+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
96
|
+
);
|
|
97
|
+
--> statement-breakpoint
|
|
98
|
+
CREATE TABLE "issue_comments" (
|
|
99
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
100
|
+
"issue_id" text NOT NULL REFERENCES "issues"("id") ON DELETE CASCADE,
|
|
101
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
102
|
+
"author_type" text NOT NULL,
|
|
103
|
+
"author_id" text,
|
|
104
|
+
"recipients_json" text DEFAULT '[]' NOT NULL,
|
|
105
|
+
"run_id" text,
|
|
106
|
+
"body" text NOT NULL,
|
|
107
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
108
|
+
);
|
|
109
|
+
--> statement-breakpoint
|
|
110
|
+
CREATE TABLE "issue_attachments" (
|
|
111
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
112
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
113
|
+
"issue_id" text NOT NULL REFERENCES "issues"("id") ON DELETE CASCADE,
|
|
114
|
+
"project_id" text NOT NULL REFERENCES "projects"("id") ON DELETE CASCADE,
|
|
115
|
+
"file_name" text NOT NULL,
|
|
116
|
+
"mime_type" text,
|
|
117
|
+
"file_size_bytes" integer NOT NULL,
|
|
118
|
+
"relative_path" text NOT NULL,
|
|
119
|
+
"uploaded_by_actor_type" text DEFAULT 'human' NOT NULL,
|
|
120
|
+
"uploaded_by_actor_id" text,
|
|
121
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
122
|
+
);
|
|
123
|
+
--> statement-breakpoint
|
|
124
|
+
CREATE TABLE "activity_logs" (
|
|
125
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
126
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
127
|
+
"issue_id" text REFERENCES "issues"("id") ON DELETE SET NULL,
|
|
128
|
+
"actor_type" text NOT NULL,
|
|
129
|
+
"actor_id" text,
|
|
130
|
+
"event_type" text NOT NULL,
|
|
131
|
+
"payload_json" text DEFAULT '{}' NOT NULL,
|
|
132
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
133
|
+
);
|
|
134
|
+
--> statement-breakpoint
|
|
135
|
+
CREATE TABLE "heartbeat_runs" (
|
|
136
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
137
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
138
|
+
"agent_id" text NOT NULL REFERENCES "agents"("id") ON DELETE CASCADE,
|
|
139
|
+
"status" text DEFAULT 'started' NOT NULL,
|
|
140
|
+
"started_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
141
|
+
"finished_at" timestamp,
|
|
142
|
+
"message" text
|
|
143
|
+
);
|
|
144
|
+
--> statement-breakpoint
|
|
145
|
+
CREATE TABLE "heartbeat_run_queue" (
|
|
146
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
147
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
148
|
+
"agent_id" text NOT NULL REFERENCES "agents"("id") ON DELETE CASCADE,
|
|
149
|
+
"job_type" text NOT NULL,
|
|
150
|
+
"payload_json" text DEFAULT '{}' NOT NULL,
|
|
151
|
+
"status" text DEFAULT 'pending' NOT NULL,
|
|
152
|
+
"priority" integer DEFAULT 100 NOT NULL,
|
|
153
|
+
"idempotency_key" text,
|
|
154
|
+
"available_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
155
|
+
"attempt_count" integer DEFAULT 0 NOT NULL,
|
|
156
|
+
"max_attempts" integer DEFAULT 10 NOT NULL,
|
|
157
|
+
"last_error" text,
|
|
158
|
+
"started_at" timestamp,
|
|
159
|
+
"finished_at" timestamp,
|
|
160
|
+
"heartbeat_run_id" text REFERENCES "heartbeat_runs"("id") ON DELETE SET NULL,
|
|
161
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
162
|
+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
163
|
+
);
|
|
164
|
+
--> statement-breakpoint
|
|
165
|
+
CREATE TABLE "heartbeat_run_messages" (
|
|
166
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
167
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
168
|
+
"run_id" text NOT NULL REFERENCES "heartbeat_runs"("id") ON DELETE CASCADE,
|
|
169
|
+
"sequence" integer NOT NULL,
|
|
170
|
+
"kind" text NOT NULL,
|
|
171
|
+
"label" text,
|
|
172
|
+
"text" text,
|
|
173
|
+
"payload_json" text,
|
|
174
|
+
"signal_level" text,
|
|
175
|
+
"group_key" text,
|
|
176
|
+
"source" text,
|
|
177
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
178
|
+
);
|
|
179
|
+
--> statement-breakpoint
|
|
180
|
+
CREATE TABLE "approval_requests" (
|
|
181
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
182
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
183
|
+
"requested_by_agent_id" text,
|
|
184
|
+
"action" text NOT NULL,
|
|
185
|
+
"payload_json" text DEFAULT '{}' NOT NULL,
|
|
186
|
+
"status" text DEFAULT 'pending' NOT NULL,
|
|
187
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
188
|
+
"resolved_at" timestamp
|
|
189
|
+
);
|
|
190
|
+
--> statement-breakpoint
|
|
191
|
+
CREATE TABLE "approval_inbox_states" (
|
|
192
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
193
|
+
"actor_id" text NOT NULL,
|
|
194
|
+
"approval_id" text NOT NULL REFERENCES "approval_requests"("id") ON DELETE CASCADE,
|
|
195
|
+
"seen_at" timestamp,
|
|
196
|
+
"dismissed_at" timestamp,
|
|
197
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
198
|
+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
199
|
+
PRIMARY KEY ("company_id", "actor_id", "approval_id")
|
|
200
|
+
);
|
|
201
|
+
--> statement-breakpoint
|
|
202
|
+
CREATE TABLE "attention_inbox_states" (
|
|
203
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
204
|
+
"actor_id" text NOT NULL,
|
|
205
|
+
"item_key" text NOT NULL,
|
|
206
|
+
"seen_at" timestamp,
|
|
207
|
+
"acknowledged_at" timestamp,
|
|
208
|
+
"dismissed_at" timestamp,
|
|
209
|
+
"resolved_at" timestamp,
|
|
210
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
211
|
+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
212
|
+
PRIMARY KEY ("company_id", "actor_id", "item_key")
|
|
213
|
+
);
|
|
214
|
+
--> statement-breakpoint
|
|
215
|
+
CREATE TABLE "cost_ledger" (
|
|
216
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
217
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
218
|
+
"run_id" text REFERENCES "heartbeat_runs"("id") ON DELETE SET NULL,
|
|
219
|
+
"project_id" text REFERENCES "projects"("id") ON DELETE SET NULL,
|
|
220
|
+
"issue_id" text REFERENCES "issues"("id") ON DELETE SET NULL,
|
|
221
|
+
"agent_id" text REFERENCES "agents"("id") ON DELETE SET NULL,
|
|
222
|
+
"provider_type" text NOT NULL,
|
|
223
|
+
"runtime_model_id" text,
|
|
224
|
+
"pricing_provider_type" text,
|
|
225
|
+
"pricing_model_id" text,
|
|
226
|
+
"pricing_source" text,
|
|
227
|
+
"token_input" integer DEFAULT 0 NOT NULL,
|
|
228
|
+
"token_output" integer DEFAULT 0 NOT NULL,
|
|
229
|
+
"usd_cost" numeric(12, 6) DEFAULT 0 NOT NULL,
|
|
230
|
+
"usd_cost_status" text,
|
|
231
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
232
|
+
);
|
|
233
|
+
--> statement-breakpoint
|
|
234
|
+
DROP TABLE IF EXISTS "model_pricing";
|
|
235
|
+
--> statement-breakpoint
|
|
236
|
+
CREATE TABLE "audit_events" (
|
|
237
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
238
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
239
|
+
"actor_type" text NOT NULL,
|
|
240
|
+
"actor_id" text,
|
|
241
|
+
"event_type" text NOT NULL,
|
|
242
|
+
"entity_type" text NOT NULL,
|
|
243
|
+
"entity_id" text NOT NULL,
|
|
244
|
+
"correlation_id" text,
|
|
245
|
+
"payload_json" text DEFAULT '{}' NOT NULL,
|
|
246
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
247
|
+
);
|
|
248
|
+
--> statement-breakpoint
|
|
249
|
+
CREATE TABLE "plugins" (
|
|
250
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
251
|
+
"name" text NOT NULL,
|
|
252
|
+
"version" text NOT NULL,
|
|
253
|
+
"kind" text NOT NULL,
|
|
254
|
+
"runtime_type" text NOT NULL,
|
|
255
|
+
"runtime_entrypoint" text NOT NULL,
|
|
256
|
+
"hooks_json" text DEFAULT '[]' NOT NULL,
|
|
257
|
+
"capabilities_json" text DEFAULT '[]' NOT NULL,
|
|
258
|
+
"manifest_json" text DEFAULT '{}' NOT NULL,
|
|
259
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
260
|
+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
261
|
+
);
|
|
262
|
+
--> statement-breakpoint
|
|
263
|
+
CREATE TABLE "templates" (
|
|
264
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
265
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
266
|
+
"slug" text NOT NULL,
|
|
267
|
+
"name" text NOT NULL,
|
|
268
|
+
"description" text,
|
|
269
|
+
"current_version" text DEFAULT '1.0.0' NOT NULL,
|
|
270
|
+
"status" text DEFAULT 'draft' NOT NULL,
|
|
271
|
+
"visibility" text DEFAULT 'company' NOT NULL,
|
|
272
|
+
"variables_json" text DEFAULT '[]' NOT NULL,
|
|
273
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
274
|
+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
275
|
+
);
|
|
276
|
+
--> statement-breakpoint
|
|
277
|
+
CREATE TABLE "template_versions" (
|
|
278
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
279
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
280
|
+
"template_id" text NOT NULL REFERENCES "templates"("id") ON DELETE CASCADE,
|
|
281
|
+
"version" text NOT NULL,
|
|
282
|
+
"manifest_json" text DEFAULT '{}' NOT NULL,
|
|
283
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
284
|
+
);
|
|
285
|
+
--> statement-breakpoint
|
|
286
|
+
CREATE TABLE "template_installs" (
|
|
287
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
288
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
289
|
+
"template_id" text REFERENCES "templates"("id") ON DELETE SET NULL,
|
|
290
|
+
"template_version_id" text REFERENCES "template_versions"("id") ON DELETE SET NULL,
|
|
291
|
+
"status" text DEFAULT 'applied' NOT NULL,
|
|
292
|
+
"summary_json" text DEFAULT '{}' NOT NULL,
|
|
293
|
+
"variables_json" text DEFAULT '{}' NOT NULL,
|
|
294
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
295
|
+
);
|
|
296
|
+
--> statement-breakpoint
|
|
297
|
+
CREATE TABLE "plugin_configs" (
|
|
298
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
299
|
+
"plugin_id" text NOT NULL REFERENCES "plugins"("id") ON DELETE CASCADE,
|
|
300
|
+
"enabled" boolean DEFAULT false NOT NULL,
|
|
301
|
+
"priority" integer DEFAULT 100 NOT NULL,
|
|
302
|
+
"config_json" text DEFAULT '{}' NOT NULL,
|
|
303
|
+
"granted_capabilities_json" text DEFAULT '[]' NOT NULL,
|
|
304
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
305
|
+
"updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
306
|
+
PRIMARY KEY ("company_id", "plugin_id")
|
|
307
|
+
);
|
|
308
|
+
--> statement-breakpoint
|
|
309
|
+
CREATE TABLE "plugin_runs" (
|
|
310
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
311
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
312
|
+
"run_id" text REFERENCES "heartbeat_runs"("id") ON DELETE CASCADE,
|
|
313
|
+
"plugin_id" text NOT NULL REFERENCES "plugins"("id") ON DELETE CASCADE,
|
|
314
|
+
"hook" text NOT NULL,
|
|
315
|
+
"status" text NOT NULL,
|
|
316
|
+
"duration_ms" integer DEFAULT 0 NOT NULL,
|
|
317
|
+
"error" text,
|
|
318
|
+
"diagnostics_json" text DEFAULT '{}' NOT NULL,
|
|
319
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
320
|
+
);
|
|
321
|
+
--> statement-breakpoint
|
|
322
|
+
CREATE TABLE "agent_issue_labels" (
|
|
323
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
324
|
+
"issue_id" text NOT NULL REFERENCES "issues"("id") ON DELETE CASCADE,
|
|
325
|
+
"label" text NOT NULL,
|
|
326
|
+
PRIMARY KEY ("company_id", "issue_id", "label")
|
|
327
|
+
);
|
|
328
|
+
--> statement-breakpoint
|
|
329
|
+
CREATE INDEX "idx_project_workspaces_company_project"
|
|
330
|
+
ON "project_workspaces" ("company_id", "project_id", "is_primary", "created_at");
|
|
331
|
+
--> statement-breakpoint
|
|
332
|
+
CREATE INDEX "idx_issues_company_status"
|
|
333
|
+
ON "issues" ("company_id", "status", "updated_at");
|
|
334
|
+
--> statement-breakpoint
|
|
335
|
+
CREATE INDEX "idx_issues_assignee_claim_priority"
|
|
336
|
+
ON "issues" ("company_id", "assignee_agent_id", "is_claimed", "status", "priority", "updated_at");
|
|
337
|
+
--> statement-breakpoint
|
|
338
|
+
CREATE INDEX "idx_issue_attachments_company_issue"
|
|
339
|
+
ON "issue_attachments" ("company_id", "issue_id", "created_at");
|
|
340
|
+
--> statement-breakpoint
|
|
341
|
+
CREATE INDEX "idx_issue_attachments_company_project"
|
|
342
|
+
ON "issue_attachments" ("company_id", "project_id", "created_at");
|
|
343
|
+
--> statement-breakpoint
|
|
344
|
+
CREATE INDEX "idx_audit_events_company_created"
|
|
345
|
+
ON "audit_events" ("company_id", "created_at");
|
|
346
|
+
--> statement-breakpoint
|
|
347
|
+
CREATE INDEX "idx_cost_ledger_company_created"
|
|
348
|
+
ON "cost_ledger" ("company_id", "created_at");
|
|
349
|
+
--> statement-breakpoint
|
|
350
|
+
CREATE UNIQUE INDEX "idx_heartbeat_runs_single_started"
|
|
351
|
+
ON "heartbeat_runs" ("company_id", "agent_id")
|
|
352
|
+
WHERE "status" = 'started';
|
|
353
|
+
--> statement-breakpoint
|
|
354
|
+
CREATE INDEX "idx_heartbeat_run_queue_status_available_priority"
|
|
355
|
+
ON "heartbeat_run_queue" ("company_id", "status", "available_at", "priority", "created_at");
|
|
356
|
+
--> statement-breakpoint
|
|
357
|
+
CREATE INDEX "idx_heartbeat_run_queue_agent_status"
|
|
358
|
+
ON "heartbeat_run_queue" ("company_id", "agent_id", "status", "available_at", "created_at");
|
|
359
|
+
--> statement-breakpoint
|
|
360
|
+
CREATE UNIQUE INDEX "idx_heartbeat_run_queue_idempotency"
|
|
361
|
+
ON "heartbeat_run_queue" ("company_id", "agent_id", "idempotency_key")
|
|
362
|
+
WHERE "idempotency_key" IS NOT NULL AND btrim("idempotency_key") <> '';
|
|
363
|
+
--> statement-breakpoint
|
|
364
|
+
CREATE INDEX "idx_heartbeat_run_messages_company_run_sequence"
|
|
365
|
+
ON "heartbeat_run_messages" ("company_id", "run_id", "sequence");
|
|
366
|
+
--> statement-breakpoint
|
|
367
|
+
CREATE INDEX "idx_heartbeat_run_messages_company_created"
|
|
368
|
+
ON "heartbeat_run_messages" ("company_id", "created_at");
|
|
369
|
+
--> statement-breakpoint
|
|
370
|
+
CREATE INDEX "idx_approval_inbox_states_company_actor_updated"
|
|
371
|
+
ON "approval_inbox_states" ("company_id", "actor_id", "updated_at");
|
|
372
|
+
--> statement-breakpoint
|
|
373
|
+
CREATE INDEX "idx_attention_inbox_states_company_actor_updated"
|
|
374
|
+
ON "attention_inbox_states" ("company_id", "actor_id", "updated_at");
|
|
375
|
+
--> statement-breakpoint
|
|
376
|
+
CREATE INDEX "idx_plugin_configs_company_enabled_priority"
|
|
377
|
+
ON "plugin_configs" ("company_id", "enabled", "priority", "plugin_id");
|
|
378
|
+
--> statement-breakpoint
|
|
379
|
+
CREATE INDEX "idx_plugin_runs_company_created"
|
|
380
|
+
ON "plugin_runs" ("company_id", "created_at");
|
|
381
|
+
--> statement-breakpoint
|
|
382
|
+
CREATE UNIQUE INDEX "idx_templates_company_slug"
|
|
383
|
+
ON "templates" ("company_id", "slug");
|
|
384
|
+
--> statement-breakpoint
|
|
385
|
+
CREATE INDEX "idx_template_versions_company_template_created"
|
|
386
|
+
ON "template_versions" ("company_id", "template_id", "created_at");
|
|
387
|
+
--> statement-breakpoint
|
|
388
|
+
CREATE INDEX "idx_template_installs_company_created"
|
|
389
|
+
ON "template_installs" ("company_id", "created_at");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "issues" ADD COLUMN "external_link" text;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
CREATE TABLE "issue_goals" (
|
|
2
|
+
"issue_id" text NOT NULL REFERENCES "issues"("id") ON DELETE CASCADE,
|
|
3
|
+
"goal_id" text NOT NULL REFERENCES "goals"("id") ON DELETE CASCADE,
|
|
4
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
5
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
6
|
+
PRIMARY KEY ("issue_id", "goal_id")
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
INSERT INTO "issue_goals" ("issue_id", "goal_id", "company_id")
|
|
10
|
+
SELECT "id", "goal_id", "company_id" FROM "issues" WHERE "goal_id" IS NOT NULL;
|
|
11
|
+
|
|
12
|
+
ALTER TABLE "issues" DROP COLUMN "goal_id";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "7",
|
|
3
|
+
"dialect": "postgresql",
|
|
4
|
+
"entries": [
|
|
5
|
+
{
|
|
6
|
+
"idx": 0,
|
|
7
|
+
"version": "7",
|
|
8
|
+
"when": 1742500000000,
|
|
9
|
+
"tag": "0000_initial",
|
|
10
|
+
"breakpoints": true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"idx": 1,
|
|
14
|
+
"version": "7",
|
|
15
|
+
"when": 1742600000000,
|
|
16
|
+
"tag": "0001_issues_external_link",
|
|
17
|
+
"breakpoints": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"idx": 2,
|
|
21
|
+
"version": "7",
|
|
22
|
+
"when": 1742700000000,
|
|
23
|
+
"tag": "0002_issues_goal_goals_owner_agent",
|
|
24
|
+
"breakpoints": true
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"idx": 3,
|
|
28
|
+
"version": "7",
|
|
29
|
+
"when": 1742800000000,
|
|
30
|
+
"tag": "0003_issue_goals_junction",
|
|
31
|
+
"breakpoints": true
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
package/src/ping.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import type { BopoDb } from "./client";
|
|
3
|
+
|
|
4
|
+
/** Cheap connection liveness check (no table scan). Kept as SQL because Drizzle has no relational API for a table-free SELECT. */
|
|
5
|
+
export async function pingDatabase(db: BopoDb): Promise<void> {
|
|
6
|
+
await db.execute(sql`SELECT 1`);
|
|
7
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { desc, eq } from "drizzle-orm";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import type { BopoDb } from "../client";
|
|
4
|
+
import { companies } from "../schema";
|
|
5
|
+
import { compactUpdate } from "./helpers";
|
|
6
|
+
|
|
7
|
+
export async function createCompany(db: BopoDb, input: { name: string; mission?: string | null }) {
|
|
8
|
+
const id = nanoid(12);
|
|
9
|
+
await db.insert(companies).values({
|
|
10
|
+
id,
|
|
11
|
+
name: input.name,
|
|
12
|
+
mission: input.mission ?? null
|
|
13
|
+
});
|
|
14
|
+
return { id, ...input };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function listCompanies(db: BopoDb) {
|
|
18
|
+
return db.select().from(companies).orderBy(desc(companies.createdAt));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function getCompany(db: BopoDb, id: string) {
|
|
22
|
+
const [row] = await db.select().from(companies).where(eq(companies.id, id)).limit(1);
|
|
23
|
+
return row ?? null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function updateCompany(
|
|
27
|
+
db: BopoDb,
|
|
28
|
+
input: { id: string; name?: string; mission?: string | null }
|
|
29
|
+
) {
|
|
30
|
+
const [company] = await db
|
|
31
|
+
.update(companies)
|
|
32
|
+
.set(compactUpdate({ name: input.name, mission: input.mission }))
|
|
33
|
+
.where(eq(companies.id, input.id))
|
|
34
|
+
.returning();
|
|
35
|
+
return company ?? null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function deleteCompany(db: BopoDb, id: string) {
|
|
39
|
+
const [deletedCompany] = await db.delete(companies).where(eq(companies.id, id)).returning({ id: companies.id });
|
|
40
|
+
return Boolean(deletedCompany);
|
|
41
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { and, eq } from "drizzle-orm";
|
|
2
|
+
import type { BopoDb } from "../client";
|
|
3
|
+
import { agents, goals, issues, projects, templates } from "../schema";
|
|
4
|
+
|
|
5
|
+
export class RepositoryValidationError extends Error {
|
|
6
|
+
constructor(message: string) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "RepositoryValidationError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function assertProjectBelongsToCompany(db: BopoDb, companyId: string, projectId: string) {
|
|
13
|
+
const [project] = await db
|
|
14
|
+
.select({ id: projects.id })
|
|
15
|
+
.from(projects)
|
|
16
|
+
.where(and(eq(projects.companyId, companyId), eq(projects.id, projectId)))
|
|
17
|
+
.limit(1);
|
|
18
|
+
if (!project) {
|
|
19
|
+
throw new RepositoryValidationError("Project not found for company.");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function assertIssueBelongsToCompany(db: BopoDb, companyId: string, issueId: string) {
|
|
24
|
+
const [issue] = await db
|
|
25
|
+
.select({ id: issues.id })
|
|
26
|
+
.from(issues)
|
|
27
|
+
.where(and(eq(issues.companyId, companyId), eq(issues.id, issueId)))
|
|
28
|
+
.limit(1);
|
|
29
|
+
if (!issue) {
|
|
30
|
+
throw new RepositoryValidationError("Issue not found for company.");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function assertGoalBelongsToCompany(db: BopoDb, companyId: string, goalId: string) {
|
|
35
|
+
const [goal] = await db
|
|
36
|
+
.select({ id: goals.id })
|
|
37
|
+
.from(goals)
|
|
38
|
+
.where(and(eq(goals.companyId, companyId), eq(goals.id, goalId)))
|
|
39
|
+
.limit(1);
|
|
40
|
+
if (!goal) {
|
|
41
|
+
throw new RepositoryValidationError("Parent goal not found for company.");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Ensures a goal can be linked to an issue: same company; project-scoped goals must match the issue's project. */
|
|
46
|
+
export async function assertIssueGoalAssignable(
|
|
47
|
+
db: BopoDb,
|
|
48
|
+
companyId: string,
|
|
49
|
+
issueProjectId: string,
|
|
50
|
+
goalId: string | null | undefined
|
|
51
|
+
) {
|
|
52
|
+
if (!goalId) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const [goal] = await db
|
|
56
|
+
.select({ id: goals.id, projectId: goals.projectId })
|
|
57
|
+
.from(goals)
|
|
58
|
+
.where(and(eq(goals.companyId, companyId), eq(goals.id, goalId)))
|
|
59
|
+
.limit(1);
|
|
60
|
+
if (!goal) {
|
|
61
|
+
throw new RepositoryValidationError("Goal not found for company.");
|
|
62
|
+
}
|
|
63
|
+
if (goal.projectId && goal.projectId !== issueProjectId) {
|
|
64
|
+
throw new RepositoryValidationError("Goal is scoped to a different project than this issue.");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Validates each goal can be linked to an issue (same company; project goals must match issue project). */
|
|
69
|
+
export async function assertIssueGoalsAssignable(
|
|
70
|
+
db: BopoDb,
|
|
71
|
+
companyId: string,
|
|
72
|
+
issueProjectId: string,
|
|
73
|
+
goalIds: string[]
|
|
74
|
+
) {
|
|
75
|
+
for (const goalId of goalIds) {
|
|
76
|
+
await assertIssueGoalAssignable(db, companyId, issueProjectId, goalId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function assertAgentBelongsToCompany(db: BopoDb, companyId: string, agentId: string) {
|
|
81
|
+
const [agent] = await db
|
|
82
|
+
.select({ id: agents.id })
|
|
83
|
+
.from(agents)
|
|
84
|
+
.where(and(eq(agents.companyId, companyId), eq(agents.id, agentId)))
|
|
85
|
+
.limit(1);
|
|
86
|
+
if (!agent) {
|
|
87
|
+
throw new RepositoryValidationError("Agent not found for company.");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function assertTemplateBelongsToCompany(db: BopoDb, companyId: string, templateId: string) {
|
|
92
|
+
const [template] = await db
|
|
93
|
+
.select({ id: templates.id })
|
|
94
|
+
.from(templates)
|
|
95
|
+
.where(and(eq(templates.companyId, companyId), eq(templates.id, templateId)))
|
|
96
|
+
.limit(1);
|
|
97
|
+
if (!template) {
|
|
98
|
+
throw new RepositoryValidationError("Template not found for company.");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function compactUpdate<T extends Record<string, unknown>>(input: T) {
|
|
103
|
+
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
|
|
104
|
+
}
|