bopodev-db 0.1.30 → 0.1.31

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bopodev-db",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -0,0 +1,67 @@
1
+ CREATE TABLE "work_loops" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
4
+ "project_id" text NOT NULL REFERENCES "projects"("id") ON DELETE CASCADE,
5
+ "parent_issue_id" text REFERENCES "issues"("id") ON DELETE SET NULL,
6
+ "goal_ids_json" text DEFAULT '[]' NOT NULL,
7
+ "title" text NOT NULL,
8
+ "description" text,
9
+ "assignee_agent_id" text NOT NULL REFERENCES "agents"("id") ON DELETE RESTRICT,
10
+ "priority" text DEFAULT 'medium' NOT NULL,
11
+ "status" text DEFAULT 'active' NOT NULL,
12
+ "concurrency_policy" text DEFAULT 'coalesce_if_active' NOT NULL,
13
+ "catch_up_policy" text DEFAULT 'skip_missed' NOT NULL,
14
+ "last_triggered_at" timestamp,
15
+ "created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
16
+ "updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
17
+ );
18
+ --> statement-breakpoint
19
+ CREATE TABLE "work_loop_triggers" (
20
+ "id" text PRIMARY KEY NOT NULL,
21
+ "company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
22
+ "work_loop_id" text NOT NULL REFERENCES "work_loops"("id") ON DELETE CASCADE,
23
+ "kind" text DEFAULT 'schedule' NOT NULL,
24
+ "label" text,
25
+ "enabled" boolean DEFAULT true NOT NULL,
26
+ "cron_expression" text NOT NULL,
27
+ "timezone" text DEFAULT 'UTC' NOT NULL,
28
+ "next_run_at" timestamp,
29
+ "last_fired_at" timestamp,
30
+ "last_result" text,
31
+ "created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
32
+ "updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
33
+ );
34
+ --> statement-breakpoint
35
+ CREATE TABLE "work_loop_runs" (
36
+ "id" text PRIMARY KEY NOT NULL,
37
+ "company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
38
+ "work_loop_id" text NOT NULL REFERENCES "work_loops"("id") ON DELETE CASCADE,
39
+ "trigger_id" text REFERENCES "work_loop_triggers"("id") ON DELETE SET NULL,
40
+ "source" text NOT NULL,
41
+ "status" text DEFAULT 'received' NOT NULL,
42
+ "triggered_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
43
+ "idempotency_key" text,
44
+ "payload_json" text DEFAULT '{}' NOT NULL,
45
+ "linked_issue_id" text REFERENCES "issues"("id") ON DELETE SET NULL,
46
+ "coalesced_into_run_id" text REFERENCES "work_loop_runs"("id") ON DELETE SET NULL,
47
+ "failure_reason" text,
48
+ "completed_at" timestamp,
49
+ "created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
50
+ "updated_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
51
+ );
52
+ --> statement-breakpoint
53
+ ALTER TABLE "issues" ADD COLUMN "loop_id" text REFERENCES "work_loops"("id") ON DELETE SET NULL;
54
+ --> statement-breakpoint
55
+ ALTER TABLE "issues" ADD COLUMN "loop_run_id" text REFERENCES "work_loop_runs"("id") ON DELETE SET NULL;
56
+ --> statement-breakpoint
57
+ CREATE INDEX "work_loops_company_status_idx" ON "work_loops" ("company_id","status");
58
+ --> statement-breakpoint
59
+ CREATE INDEX "work_loop_triggers_company_next_run_idx" ON "work_loop_triggers" ("company_id","next_run_at");
60
+ --> statement-breakpoint
61
+ CREATE INDEX "work_loop_triggers_work_loop_idx" ON "work_loop_triggers" ("work_loop_id");
62
+ --> statement-breakpoint
63
+ CREATE INDEX "work_loop_runs_work_loop_created_idx" ON "work_loop_runs" ("work_loop_id","created_at");
64
+ --> statement-breakpoint
65
+ CREATE INDEX "work_loop_runs_trigger_idempotency_idx" ON "work_loop_runs" ("trigger_id","idempotency_key");
66
+ --> statement-breakpoint
67
+ CREATE INDEX "issues_loop_id_idx" ON "issues" ("loop_id");
@@ -36,6 +36,13 @@
36
36
  "when": 1742900000000,
37
37
  "tag": "0004_agents_capabilities",
38
38
  "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "7",
43
+ "when": 1743000000000,
44
+ "tag": "0005_work_loops",
45
+ "breakpoints": true
39
46
  }
40
47
  ]
41
48
  }
@@ -521,6 +521,8 @@ export async function createIssue(
521
521
  assigneeAgentId?: string | null;
522
522
  labels?: string[];
523
523
  tags?: string[];
524
+ loopId?: string | null;
525
+ loopRunId?: string | null;
524
526
  }
525
527
  ) {
526
528
  await assertProjectBelongsToCompany(db, input.companyId, input.projectId);
@@ -546,7 +548,9 @@ export async function createIssue(
546
548
  priority: input.priority ?? "none",
547
549
  assigneeAgentId: input.assigneeAgentId ?? null,
548
550
  labelsJson: JSON.stringify(input.labels ?? []),
549
- tagsJson: JSON.stringify(input.tags ?? [])
551
+ tagsJson: JSON.stringify(input.tags ?? []),
552
+ loopId: input.loopId ?? null,
553
+ loopRunId: input.loopRunId ?? null
550
554
  })
551
555
  .returning();
552
556
  if (!row) {
package/src/schema.ts CHANGED
@@ -121,6 +121,75 @@ export const issues = pgTable("issues", {
121
121
  externalLink: text("external_link"),
122
122
  isClaimed: boolean("is_claimed").notNull().default(false),
123
123
  claimedByHeartbeatRunId: text("claimed_by_heartbeat_run_id"),
124
+ /** Set when issue was created by a scheduled/manual work loop run (FK enforced in SQL migration). */
125
+ loopId: text("loop_id"),
126
+ loopRunId: text("loop_run_id"),
127
+ createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
128
+ updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull()
129
+ });
130
+
131
+ export const workLoops = pgTable("work_loops", {
132
+ id: text("id").primaryKey(),
133
+ companyId: text("company_id")
134
+ .notNull()
135
+ .references(() => companies.id, { onDelete: "cascade" }),
136
+ projectId: text("project_id")
137
+ .notNull()
138
+ .references(() => projects.id, { onDelete: "cascade" }),
139
+ parentIssueId: text("parent_issue_id").references(() => issues.id, { onDelete: "set null" }),
140
+ goalIdsJson: text("goal_ids_json").notNull().default("[]"),
141
+ title: text("title").notNull(),
142
+ description: text("description"),
143
+ assigneeAgentId: text("assignee_agent_id")
144
+ .notNull()
145
+ .references(() => agents.id, { onDelete: "restrict" }),
146
+ priority: text("priority").notNull().default("medium"),
147
+ status: text("status").notNull().default("active"),
148
+ concurrencyPolicy: text("concurrency_policy").notNull().default("coalesce_if_active"),
149
+ catchUpPolicy: text("catch_up_policy").notNull().default("skip_missed"),
150
+ lastTriggeredAt: timestamp("last_triggered_at", { mode: "date" }),
151
+ createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
152
+ updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull()
153
+ });
154
+
155
+ export const workLoopTriggers = pgTable("work_loop_triggers", {
156
+ id: text("id").primaryKey(),
157
+ companyId: text("company_id")
158
+ .notNull()
159
+ .references(() => companies.id, { onDelete: "cascade" }),
160
+ workLoopId: text("work_loop_id")
161
+ .notNull()
162
+ .references(() => workLoops.id, { onDelete: "cascade" }),
163
+ kind: text("kind").notNull().default("schedule"),
164
+ label: text("label"),
165
+ enabled: boolean("enabled").notNull().default(true),
166
+ cronExpression: text("cron_expression").notNull(),
167
+ timezone: text("timezone").notNull().default("UTC"),
168
+ nextRunAt: timestamp("next_run_at", { mode: "date" }),
169
+ lastFiredAt: timestamp("last_fired_at", { mode: "date" }),
170
+ lastResult: text("last_result"),
171
+ createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
172
+ updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull()
173
+ });
174
+
175
+ export const workLoopRuns = pgTable("work_loop_runs", {
176
+ id: text("id").primaryKey(),
177
+ companyId: text("company_id")
178
+ .notNull()
179
+ .references(() => companies.id, { onDelete: "cascade" }),
180
+ workLoopId: text("work_loop_id")
181
+ .notNull()
182
+ .references(() => workLoops.id, { onDelete: "cascade" }),
183
+ triggerId: text("trigger_id").references(() => workLoopTriggers.id, { onDelete: "set null" }),
184
+ source: text("source").notNull(),
185
+ status: text("status").notNull().default("received"),
186
+ triggeredAt: timestamp("triggered_at", { mode: "date" }).defaultNow().notNull(),
187
+ idempotencyKey: text("idempotency_key"),
188
+ payloadJson: text("payload_json").notNull().default("{}"),
189
+ linkedIssueId: text("linked_issue_id").references(() => issues.id, { onDelete: "set null" }),
190
+ coalescedIntoRunId: text("coalesced_into_run_id"),
191
+ failureReason: text("failure_reason"),
192
+ completedAt: timestamp("completed_at", { mode: "date" }),
124
193
  createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
125
194
  updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull()
126
195
  });
@@ -445,6 +514,9 @@ export const schema = {
445
514
  goals,
446
515
  agents,
447
516
  issues,
517
+ workLoops,
518
+ workLoopTriggers,
519
+ workLoopRuns,
448
520
  issueGoals,
449
521
  issueComments,
450
522
  issueAttachments,