bopodev-db 0.1.29 → 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.29",
3
+ "version": "0.1.31",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -0,0 +1 @@
1
+ ALTER TABLE "agents" ADD COLUMN "capabilities" text;
@@ -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");
@@ -29,6 +29,20 @@
29
29
  "when": 1742800000000,
30
30
  "tag": "0003_issue_goals_junction",
31
31
  "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "7",
36
+ "when": 1742900000000,
37
+ "tag": "0004_agents_capabilities",
38
+ "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "7",
43
+ "when": 1743000000000,
44
+ "tag": "0005_work_loops",
45
+ "breakpoints": true
32
46
  }
33
47
  ]
34
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) {
@@ -1032,6 +1036,7 @@ export async function createAgent(
1032
1036
  role: string;
1033
1037
  roleKey?: string | null;
1034
1038
  title?: string | null;
1039
+ capabilities?: string | null;
1035
1040
  name: string;
1036
1041
  providerType:
1037
1042
  | "claude_code"
@@ -1041,6 +1046,7 @@ export async function createAgent(
1041
1046
  | "gemini_cli"
1042
1047
  | "openai_api"
1043
1048
  | "anthropic_api"
1049
+ | "openclaw_gateway"
1044
1050
  | "http"
1045
1051
  | "shell";
1046
1052
  heartbeatCron: string;
@@ -1072,6 +1078,7 @@ export async function createAgent(
1072
1078
  role: input.role,
1073
1079
  roleKey: input.roleKey ?? null,
1074
1080
  title: input.title ?? null,
1081
+ capabilities: input.capabilities ?? null,
1075
1082
  name: input.name,
1076
1083
  providerType: input.providerType,
1077
1084
  heartbeatCron: input.heartbeatCron,
@@ -1107,6 +1114,7 @@ export async function updateAgent(
1107
1114
  role?: string;
1108
1115
  roleKey?: string | null;
1109
1116
  title?: string | null;
1117
+ capabilities?: string | null;
1110
1118
  name?: string;
1111
1119
  providerType?:
1112
1120
  | "claude_code"
@@ -1116,6 +1124,7 @@ export async function updateAgent(
1116
1124
  | "gemini_cli"
1117
1125
  | "openai_api"
1118
1126
  | "anthropic_api"
1127
+ | "openclaw_gateway"
1119
1128
  | "http"
1120
1129
  | "shell";
1121
1130
  status?: string;
@@ -1146,6 +1155,7 @@ export async function updateAgent(
1146
1155
  role: input.role,
1147
1156
  roleKey: input.roleKey,
1148
1157
  title: input.title,
1158
+ capabilities: input.capabilities,
1149
1159
  name: input.name,
1150
1160
  providerType: input.providerType,
1151
1161
  status: input.status,
package/src/schema.ts CHANGED
@@ -72,6 +72,7 @@ export const agents = pgTable("agents", {
72
72
  role: text("role").notNull(),
73
73
  roleKey: text("role_key"),
74
74
  title: text("title"),
75
+ capabilities: text("capabilities"),
75
76
  name: text("name").notNull(),
76
77
  providerType: text("provider_type").notNull(),
77
78
  status: text("status").notNull().default("idle"),
@@ -120,6 +121,75 @@ export const issues = pgTable("issues", {
120
121
  externalLink: text("external_link"),
121
122
  isClaimed: boolean("is_claimed").notNull().default(false),
122
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" }),
123
193
  createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
124
194
  updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull()
125
195
  });
@@ -444,6 +514,9 @@ export const schema = {
444
514
  goals,
445
515
  agents,
446
516
  issues,
517
+ workLoops,
518
+ workLoopTriggers,
519
+ workLoopRuns,
447
520
  issueGoals,
448
521
  issueComments,
449
522
  issueAttachments,