bopodev-db 0.1.34 → 0.1.36
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-typecheck.log +1 -1
- package/dist/load-migrate-env.d.ts +5 -0
- package/dist/repositories/company-assistant-chat.d.ts +10 -0
- package/dist/repositories/legacy.d.ts +124 -10
- package/dist/schema.d.ts +566 -8
- package/package.json +2 -1
- package/src/load-migrate-env.ts +42 -0
- package/src/migrate.ts +17 -0
- package/src/migrations/0009_agent_issue_permissions.sql +2 -0
- package/src/migrations/0010_agent_lucide_icon.sql +1 -0
- package/src/migrations/0011_plugin_installs.sql +17 -0
- package/src/migrations/0012_issue_knowledge_paths.sql +1 -0
- package/src/migrations/meta/_journal.json +28 -0
- package/src/repositories/company-assistant-chat.ts +51 -1
- package/src/repositories/legacy.ts +149 -5
- package/src/schema.ts +30 -5
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { config as loadDotenv } from "dotenv";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Load the same env files as `apps/api` (`loadApiEnv`), so `db:migrate` targets the DB
|
|
8
|
+
* the API uses when developers run migrations from the shell without exporting variables.
|
|
9
|
+
*/
|
|
10
|
+
export function loadMigrateEnv() {
|
|
11
|
+
const roots = resolveCandidateRepoRoots();
|
|
12
|
+
for (const root of roots) {
|
|
13
|
+
for (const name of [".env.local", ".env"] as const) {
|
|
14
|
+
loadDotenv({ path: join(root, name), override: false, quiet: true });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveCandidateRepoRoots(): string[] {
|
|
20
|
+
const fromPackage = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
|
|
21
|
+
const ordered: string[] = [fromPackage];
|
|
22
|
+
const fromCwd = findRepoRootWalkingFromCwd();
|
|
23
|
+
if (fromCwd && resolve(fromCwd) !== resolve(fromPackage)) {
|
|
24
|
+
ordered.push(fromCwd);
|
|
25
|
+
}
|
|
26
|
+
return ordered;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function findRepoRootWalkingFromCwd(): string | null {
|
|
30
|
+
let dir = resolve(process.cwd());
|
|
31
|
+
for (let i = 0; i < 12; i++) {
|
|
32
|
+
if (existsSync(join(dir, "pnpm-workspace.yaml"))) {
|
|
33
|
+
return dir;
|
|
34
|
+
}
|
|
35
|
+
const parent = dirname(dir);
|
|
36
|
+
if (parent === dir) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
dir = parent;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
package/src/migrate.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { applyDatabaseMigrations, createDb } from "./client";
|
|
2
|
+
import { resolveDefaultDbPath } from "./default-paths";
|
|
3
|
+
import { loadMigrateEnv } from "./load-migrate-env";
|
|
2
4
|
|
|
3
5
|
async function main() {
|
|
6
|
+
loadMigrateEnv();
|
|
7
|
+
logMigrationTarget();
|
|
4
8
|
const dbPath = normalizeOptionalDbPath(process.env.BOPO_DB_PATH);
|
|
5
9
|
const connection = await createDb(dbPath);
|
|
6
10
|
try {
|
|
@@ -18,3 +22,16 @@ function normalizeOptionalDbPath(value: string | undefined) {
|
|
|
18
22
|
const normalized = value?.trim();
|
|
19
23
|
return normalized && normalized.length > 0 ? normalized : undefined;
|
|
20
24
|
}
|
|
25
|
+
|
|
26
|
+
function logMigrationTarget() {
|
|
27
|
+
const external = process.env.DATABASE_URL?.trim();
|
|
28
|
+
if (external) {
|
|
29
|
+
// eslint-disable-next-line no-console
|
|
30
|
+
console.log("[bopodev-db] Migrating database from DATABASE_URL (same sources as API: .env.local, .env at repo root).");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const configured = process.env.BOPO_DB_PATH?.trim();
|
|
34
|
+
const dataPath = configured && configured.length > 0 ? configured : resolveDefaultDbPath();
|
|
35
|
+
// eslint-disable-next-line no-console
|
|
36
|
+
console.log(`[bopodev-db] Migrating embedded Postgres at ${dataPath} (set DATABASE_URL to use an external Postgres).`);
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "agents" ADD COLUMN "lucide_icon_name" text DEFAULT '' NOT NULL;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
CREATE TABLE "plugin_installs" (
|
|
2
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
3
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
4
|
+
"plugin_id" text NOT NULL REFERENCES "plugins"("id") ON DELETE CASCADE,
|
|
5
|
+
"plugin_version" text NOT NULL,
|
|
6
|
+
"source_type" text DEFAULT 'registry' NOT NULL,
|
|
7
|
+
"source_ref" text,
|
|
8
|
+
"integrity" text,
|
|
9
|
+
"build_hash" text,
|
|
10
|
+
"artifact_path" text,
|
|
11
|
+
"manifest_json" text DEFAULT '{}' NOT NULL,
|
|
12
|
+
"status" text DEFAULT 'active' NOT NULL,
|
|
13
|
+
"created_at" timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
14
|
+
);
|
|
15
|
+
--> statement-breakpoint
|
|
16
|
+
CREATE INDEX "idx_plugin_installs_company_plugin_created"
|
|
17
|
+
ON "plugin_installs" ("company_id", "plugin_id", "created_at");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "issues" ADD COLUMN IF NOT EXISTS "knowledge_paths_json" text NOT NULL DEFAULT '[]';
|
|
@@ -64,6 +64,34 @@
|
|
|
64
64
|
"when": 1743300000000,
|
|
65
65
|
"tag": "0008_goals_parent_goal_fk",
|
|
66
66
|
"breakpoints": true
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"idx": 9,
|
|
70
|
+
"version": "7",
|
|
71
|
+
"when": 1743400000000,
|
|
72
|
+
"tag": "0009_agent_issue_permissions",
|
|
73
|
+
"breakpoints": true
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"idx": 10,
|
|
77
|
+
"version": "7",
|
|
78
|
+
"when": 1743500000000,
|
|
79
|
+
"tag": "0010_agent_lucide_icon",
|
|
80
|
+
"breakpoints": true
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"idx": 11,
|
|
84
|
+
"version": "7",
|
|
85
|
+
"when": 1743600000000,
|
|
86
|
+
"tag": "0011_plugin_installs",
|
|
87
|
+
"breakpoints": true
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"idx": 12,
|
|
91
|
+
"version": "7",
|
|
92
|
+
"when": 1743700000000,
|
|
93
|
+
"tag": "0012_issue_knowledge_paths",
|
|
94
|
+
"breakpoints": true
|
|
67
95
|
}
|
|
68
96
|
]
|
|
69
97
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, asc, count, desc, eq, gte, lt } from "drizzle-orm";
|
|
1
|
+
import { and, asc, count, desc, eq, gte, lt, sql } from "drizzle-orm";
|
|
2
2
|
import { nanoid } from "nanoid";
|
|
3
3
|
import type { BopoDb } from "../client";
|
|
4
4
|
import { companyAssistantMessages, companyAssistantThreads } from "../schema";
|
|
@@ -45,6 +45,56 @@ export async function getAssistantThreadById(db: BopoDb, companyId: string, thre
|
|
|
45
45
|
return row ?? null;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/** Removes the thread and its messages (cascade). Returns whether a row was deleted. */
|
|
49
|
+
export async function deleteAssistantThread(db: BopoDb, companyId: string, threadId: string): Promise<boolean> {
|
|
50
|
+
const [row] = await db
|
|
51
|
+
.delete(companyAssistantThreads)
|
|
52
|
+
.where(and(eq(companyAssistantThreads.id, threadId), eq(companyAssistantThreads.companyId, companyId)))
|
|
53
|
+
.returning({ id: companyAssistantThreads.id });
|
|
54
|
+
return Boolean(row);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type AssistantThreadSummary = {
|
|
58
|
+
id: string;
|
|
59
|
+
createdAt: Date;
|
|
60
|
+
updatedAt: Date;
|
|
61
|
+
previewBody: string | null;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** Threads for the company, newest activity first, with last message body as preview (if any). */
|
|
65
|
+
export async function listAssistantThreadsForCompany(
|
|
66
|
+
db: BopoDb,
|
|
67
|
+
companyId: string,
|
|
68
|
+
limit = 50
|
|
69
|
+
): Promise<AssistantThreadSummary[]> {
|
|
70
|
+
const capped = Math.min(Math.max(1, limit), 100);
|
|
71
|
+
const result = await db.execute(sql`
|
|
72
|
+
SELECT t.id, t.created_at, t.updated_at, lm.body AS preview_body
|
|
73
|
+
FROM company_assistant_threads t
|
|
74
|
+
LEFT JOIN LATERAL (
|
|
75
|
+
SELECT body FROM company_assistant_messages
|
|
76
|
+
WHERE thread_id = t.id
|
|
77
|
+
ORDER BY created_at DESC
|
|
78
|
+
LIMIT 1
|
|
79
|
+
) lm ON true
|
|
80
|
+
WHERE t.company_id = ${companyId}
|
|
81
|
+
ORDER BY t.updated_at DESC
|
|
82
|
+
LIMIT ${capped}
|
|
83
|
+
`);
|
|
84
|
+
const rows = result as unknown as Array<{
|
|
85
|
+
id: string;
|
|
86
|
+
created_at: Date | string;
|
|
87
|
+
updated_at: Date | string;
|
|
88
|
+
preview_body: string | null;
|
|
89
|
+
}>;
|
|
90
|
+
return rows.map((r) => ({
|
|
91
|
+
id: r.id,
|
|
92
|
+
createdAt: r.created_at instanceof Date ? r.created_at : new Date(String(r.created_at)),
|
|
93
|
+
updatedAt: r.updated_at instanceof Date ? r.updated_at : new Date(String(r.updated_at)),
|
|
94
|
+
previewBody: r.preview_body
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
|
|
48
98
|
export async function touchAssistantThread(db: BopoDb, threadId: string) {
|
|
49
99
|
await db
|
|
50
100
|
.update(companyAssistantThreads)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, asc, desc, eq, gt, gte, inArray, lt, notInArray, sql } from "drizzle-orm";
|
|
1
|
+
import { and, asc, count, desc, eq, gt, gte, inArray, lt, notInArray, sql } from "drizzle-orm";
|
|
2
2
|
import { nanoid } from "nanoid";
|
|
3
3
|
import type { BopoDb } from "../client";
|
|
4
4
|
import {
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
issueGoals,
|
|
19
19
|
issues,
|
|
20
20
|
pluginConfigs,
|
|
21
|
+
pluginInstalls,
|
|
21
22
|
pluginRuns,
|
|
22
23
|
plugins,
|
|
23
24
|
projectWorkspaces,
|
|
@@ -521,8 +522,9 @@ export async function createIssue(
|
|
|
521
522
|
assigneeAgentId?: string | null;
|
|
522
523
|
labels?: string[];
|
|
523
524
|
tags?: string[];
|
|
524
|
-
|
|
525
|
-
|
|
525
|
+
knowledgePaths?: string[];
|
|
526
|
+
routineId?: string | null;
|
|
527
|
+
routineRunId?: string | null;
|
|
526
528
|
}
|
|
527
529
|
) {
|
|
528
530
|
await assertProjectBelongsToCompany(db, input.companyId, input.projectId);
|
|
@@ -549,8 +551,9 @@ export async function createIssue(
|
|
|
549
551
|
assigneeAgentId: input.assigneeAgentId ?? null,
|
|
550
552
|
labelsJson: JSON.stringify(input.labels ?? []),
|
|
551
553
|
tagsJson: JSON.stringify(input.tags ?? []),
|
|
552
|
-
|
|
553
|
-
|
|
554
|
+
knowledgePathsJson: JSON.stringify(input.knowledgePaths ?? []),
|
|
555
|
+
routineId: input.routineId ?? null,
|
|
556
|
+
routineRunId: input.routineRunId ?? null
|
|
554
557
|
})
|
|
555
558
|
.returning();
|
|
556
559
|
if (!row) {
|
|
@@ -581,6 +584,7 @@ export async function updateIssue(
|
|
|
581
584
|
assigneeAgentId?: string | null;
|
|
582
585
|
labels?: string[];
|
|
583
586
|
tags?: string[];
|
|
587
|
+
knowledgePaths?: string[];
|
|
584
588
|
}
|
|
585
589
|
) {
|
|
586
590
|
const [existing] = await db
|
|
@@ -615,6 +619,8 @@ export async function updateIssue(
|
|
|
615
619
|
assigneeAgentId: input.assigneeAgentId,
|
|
616
620
|
labelsJson: input.labels ? JSON.stringify(input.labels) : undefined,
|
|
617
621
|
tagsJson: input.tags ? JSON.stringify(input.tags) : undefined,
|
|
622
|
+
knowledgePathsJson:
|
|
623
|
+
input.knowledgePaths !== undefined ? JSON.stringify(input.knowledgePaths) : undefined,
|
|
618
624
|
updatedAt: touchUpdatedAtSql
|
|
619
625
|
})
|
|
620
626
|
)
|
|
@@ -1079,6 +1085,8 @@ export async function createAgent(
|
|
|
1079
1085
|
heartbeatCron: string;
|
|
1080
1086
|
monthlyBudgetUsd: string;
|
|
1081
1087
|
canHireAgents?: boolean;
|
|
1088
|
+
canAssignAgents?: boolean;
|
|
1089
|
+
canCreateIssues?: boolean;
|
|
1082
1090
|
avatarSeed?: string;
|
|
1083
1091
|
runtimeCommand?: string | null;
|
|
1084
1092
|
runtimeArgsJson?: string;
|
|
@@ -1111,7 +1119,10 @@ export async function createAgent(
|
|
|
1111
1119
|
heartbeatCron: input.heartbeatCron,
|
|
1112
1120
|
monthlyBudgetUsd: input.monthlyBudgetUsd,
|
|
1113
1121
|
canHireAgents: input.canHireAgents ?? false,
|
|
1122
|
+
canAssignAgents: input.canAssignAgents ?? true,
|
|
1123
|
+
canCreateIssues: input.canCreateIssues ?? true,
|
|
1114
1124
|
avatarSeed,
|
|
1125
|
+
lucideIconName: "",
|
|
1115
1126
|
runtimeCommand: input.runtimeCommand ?? null,
|
|
1116
1127
|
runtimeArgsJson: input.runtimeArgsJson ?? "[]",
|
|
1117
1128
|
runtimeCwd: input.runtimeCwd ?? null,
|
|
@@ -1132,6 +1143,15 @@ export async function listAgents(db: BopoDb, companyId: string) {
|
|
|
1132
1143
|
return db.select().from(agents).where(eq(agents.companyId, companyId)).orderBy(desc(agents.createdAt));
|
|
1133
1144
|
}
|
|
1134
1145
|
|
|
1146
|
+
export async function getCompanyAgent(db: BopoDb, companyId: string, agentId: string) {
|
|
1147
|
+
const [row] = await db
|
|
1148
|
+
.select()
|
|
1149
|
+
.from(agents)
|
|
1150
|
+
.where(and(eq(agents.companyId, companyId), eq(agents.id, agentId)))
|
|
1151
|
+
.limit(1);
|
|
1152
|
+
return row ?? null;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1135
1155
|
export async function updateAgent(
|
|
1136
1156
|
db: BopoDb,
|
|
1137
1157
|
input: {
|
|
@@ -1158,6 +1178,8 @@ export async function updateAgent(
|
|
|
1158
1178
|
heartbeatCron?: string;
|
|
1159
1179
|
monthlyBudgetUsd?: string;
|
|
1160
1180
|
canHireAgents?: boolean;
|
|
1181
|
+
canAssignAgents?: boolean;
|
|
1182
|
+
canCreateIssues?: boolean;
|
|
1161
1183
|
runtimeCommand?: string | null;
|
|
1162
1184
|
runtimeArgsJson?: string;
|
|
1163
1185
|
runtimeCwd?: string | null;
|
|
@@ -1169,6 +1191,8 @@ export async function updateAgent(
|
|
|
1169
1191
|
interruptGraceSec?: number;
|
|
1170
1192
|
runPolicyJson?: string;
|
|
1171
1193
|
stateBlob?: Record<string, unknown>;
|
|
1194
|
+
lucideIconName?: string;
|
|
1195
|
+
avatarSeed?: string;
|
|
1172
1196
|
}
|
|
1173
1197
|
) {
|
|
1174
1198
|
if (input.managerAgentId) {
|
|
@@ -1189,6 +1213,10 @@ export async function updateAgent(
|
|
|
1189
1213
|
heartbeatCron: input.heartbeatCron,
|
|
1190
1214
|
monthlyBudgetUsd: input.monthlyBudgetUsd,
|
|
1191
1215
|
canHireAgents: input.canHireAgents,
|
|
1216
|
+
canAssignAgents: input.canAssignAgents,
|
|
1217
|
+
canCreateIssues: input.canCreateIssues,
|
|
1218
|
+
lucideIconName: input.lucideIconName,
|
|
1219
|
+
avatarSeed: input.avatarSeed,
|
|
1192
1220
|
runtimeCommand: input.runtimeCommand,
|
|
1193
1221
|
runtimeArgsJson: input.runtimeArgsJson,
|
|
1194
1222
|
runtimeCwd: input.runtimeCwd,
|
|
@@ -2303,6 +2331,122 @@ export async function appendPluginRun(
|
|
|
2303
2331
|
return id;
|
|
2304
2332
|
}
|
|
2305
2333
|
|
|
2334
|
+
export async function appendPluginInstall(
|
|
2335
|
+
db: BopoDb,
|
|
2336
|
+
input: {
|
|
2337
|
+
companyId: string;
|
|
2338
|
+
pluginId: string;
|
|
2339
|
+
pluginVersion: string;
|
|
2340
|
+
sourceType: string;
|
|
2341
|
+
sourceRef?: string | null;
|
|
2342
|
+
integrity?: string | null;
|
|
2343
|
+
buildHash?: string | null;
|
|
2344
|
+
artifactPath?: string | null;
|
|
2345
|
+
manifestJson: string;
|
|
2346
|
+
status?: "active" | "superseded" | "rolled_back";
|
|
2347
|
+
}
|
|
2348
|
+
) {
|
|
2349
|
+
const id = nanoid(14);
|
|
2350
|
+
await db.insert(pluginInstalls).values({
|
|
2351
|
+
id,
|
|
2352
|
+
companyId: input.companyId,
|
|
2353
|
+
pluginId: input.pluginId,
|
|
2354
|
+
pluginVersion: input.pluginVersion,
|
|
2355
|
+
sourceType: input.sourceType,
|
|
2356
|
+
sourceRef: input.sourceRef ?? null,
|
|
2357
|
+
integrity: input.integrity ?? null,
|
|
2358
|
+
buildHash: input.buildHash ?? null,
|
|
2359
|
+
artifactPath: input.artifactPath ?? null,
|
|
2360
|
+
manifestJson: input.manifestJson,
|
|
2361
|
+
status: input.status ?? "active"
|
|
2362
|
+
});
|
|
2363
|
+
return id;
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
export async function listPluginInstalls(
|
|
2367
|
+
db: BopoDb,
|
|
2368
|
+
input: {
|
|
2369
|
+
companyId: string;
|
|
2370
|
+
pluginId: string;
|
|
2371
|
+
limit?: number;
|
|
2372
|
+
}
|
|
2373
|
+
) {
|
|
2374
|
+
const limit = Math.min(Math.max(input.limit ?? 50, 1), 200);
|
|
2375
|
+
return db
|
|
2376
|
+
.select()
|
|
2377
|
+
.from(pluginInstalls)
|
|
2378
|
+
.where(and(eq(pluginInstalls.companyId, input.companyId), eq(pluginInstalls.pluginId, input.pluginId)))
|
|
2379
|
+
.orderBy(desc(pluginInstalls.createdAt))
|
|
2380
|
+
.limit(limit);
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
/** Per-plugin counts of `plugin_installs` rows for rollback UX (need ≥2 revisions to roll back). */
|
|
2384
|
+
export async function countPluginInstallRevisionsByCompany(db: BopoDb, companyId: string): Promise<Map<string, number>> {
|
|
2385
|
+
const rows = await db
|
|
2386
|
+
.select({
|
|
2387
|
+
pluginId: pluginInstalls.pluginId,
|
|
2388
|
+
revisionCount: count()
|
|
2389
|
+
})
|
|
2390
|
+
.from(pluginInstalls)
|
|
2391
|
+
.where(eq(pluginInstalls.companyId, companyId))
|
|
2392
|
+
.groupBy(pluginInstalls.pluginId);
|
|
2393
|
+
return new Map(rows.map((row) => [row.pluginId, Number(row.revisionCount)]));
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
export async function getPluginInstallById(
|
|
2397
|
+
db: BopoDb,
|
|
2398
|
+
input: {
|
|
2399
|
+
companyId: string;
|
|
2400
|
+
pluginId: string;
|
|
2401
|
+
installId: string;
|
|
2402
|
+
}
|
|
2403
|
+
) {
|
|
2404
|
+
const [row] = await db
|
|
2405
|
+
.select()
|
|
2406
|
+
.from(pluginInstalls)
|
|
2407
|
+
.where(
|
|
2408
|
+
and(
|
|
2409
|
+
eq(pluginInstalls.companyId, input.companyId),
|
|
2410
|
+
eq(pluginInstalls.pluginId, input.pluginId),
|
|
2411
|
+
eq(pluginInstalls.id, input.installId)
|
|
2412
|
+
)
|
|
2413
|
+
)
|
|
2414
|
+
.limit(1);
|
|
2415
|
+
return row ?? null;
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
export async function markPluginInstallsSuperseded(db: BopoDb, input: { companyId: string; pluginId: string }) {
|
|
2419
|
+
await db
|
|
2420
|
+
.update(pluginInstalls)
|
|
2421
|
+
.set({
|
|
2422
|
+
status: "superseded"
|
|
2423
|
+
})
|
|
2424
|
+
.where(and(eq(pluginInstalls.companyId, input.companyId), eq(pluginInstalls.pluginId, input.pluginId), eq(pluginInstalls.status, "active")));
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
export async function markPluginInstallStatus(
|
|
2428
|
+
db: BopoDb,
|
|
2429
|
+
input: {
|
|
2430
|
+
companyId: string;
|
|
2431
|
+
pluginId: string;
|
|
2432
|
+
installId: string;
|
|
2433
|
+
status: "active" | "superseded" | "rolled_back";
|
|
2434
|
+
}
|
|
2435
|
+
) {
|
|
2436
|
+
await db
|
|
2437
|
+
.update(pluginInstalls)
|
|
2438
|
+
.set({
|
|
2439
|
+
status: input.status
|
|
2440
|
+
})
|
|
2441
|
+
.where(
|
|
2442
|
+
and(
|
|
2443
|
+
eq(pluginInstalls.companyId, input.companyId),
|
|
2444
|
+
eq(pluginInstalls.pluginId, input.pluginId),
|
|
2445
|
+
eq(pluginInstalls.id, input.installId)
|
|
2446
|
+
)
|
|
2447
|
+
);
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2306
2450
|
export async function listPluginRuns(
|
|
2307
2451
|
db: BopoDb,
|
|
2308
2452
|
input: { companyId: string; pluginId?: string; runId?: string; limit?: number }
|
package/src/schema.ts
CHANGED
|
@@ -86,7 +86,10 @@ export const agents = pgTable("agents", {
|
|
|
86
86
|
.default("0"),
|
|
87
87
|
tokenUsage: integer("token_usage").notNull().default(0),
|
|
88
88
|
canHireAgents: boolean("can_hire_agents").notNull().default(false),
|
|
89
|
+
canAssignAgents: boolean("can_assign_agents").notNull().default(true),
|
|
90
|
+
canCreateIssues: boolean("can_create_issues").notNull().default(true),
|
|
89
91
|
avatarSeed: text("avatar_seed").notNull().default(""),
|
|
92
|
+
lucideIconName: text("lucide_icon_name").notNull().default(""),
|
|
90
93
|
runtimeCommand: text("runtime_command"),
|
|
91
94
|
runtimeArgsJson: text("runtime_args_json").notNull().default("[]"),
|
|
92
95
|
runtimeCwd: text("runtime_cwd"),
|
|
@@ -118,13 +121,15 @@ export const issues = pgTable("issues", {
|
|
|
118
121
|
assigneeAgentId: text("assignee_agent_id"),
|
|
119
122
|
labelsJson: text("labels_json").notNull().default("[]"),
|
|
120
123
|
tagsJson: text("tags_json").notNull().default("[]"),
|
|
124
|
+
/** Relative paths under company workspace `knowledge/` linked from this issue. */
|
|
125
|
+
knowledgePathsJson: text("knowledge_paths_json").notNull().default("[]"),
|
|
121
126
|
/** Optional link to a PR, branch page, or other external tracker (GitHub/GitLab/etc.). */
|
|
122
127
|
externalLink: text("external_link"),
|
|
123
128
|
isClaimed: boolean("is_claimed").notNull().default(false),
|
|
124
129
|
claimedByHeartbeatRunId: text("claimed_by_heartbeat_run_id"),
|
|
125
|
-
/** Set when issue was created by a scheduled/manual
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
/** Set when issue was created by a scheduled/manual routine run (FK enforced in SQL migration). */
|
|
131
|
+
routineId: text("loop_id"),
|
|
132
|
+
routineRunId: text("loop_run_id"),
|
|
128
133
|
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
129
134
|
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull()
|
|
130
135
|
});
|
|
@@ -158,7 +163,7 @@ export const workLoopTriggers = pgTable("work_loop_triggers", {
|
|
|
158
163
|
companyId: text("company_id")
|
|
159
164
|
.notNull()
|
|
160
165
|
.references(() => companies.id, { onDelete: "cascade" }),
|
|
161
|
-
|
|
166
|
+
routineId: text("work_loop_id")
|
|
162
167
|
.notNull()
|
|
163
168
|
.references(() => workLoops.id, { onDelete: "cascade" }),
|
|
164
169
|
kind: text("kind").notNull().default("schedule"),
|
|
@@ -178,7 +183,7 @@ export const workLoopRuns = pgTable("work_loop_runs", {
|
|
|
178
183
|
companyId: text("company_id")
|
|
179
184
|
.notNull()
|
|
180
185
|
.references(() => companies.id, { onDelete: "cascade" }),
|
|
181
|
-
|
|
186
|
+
routineId: text("work_loop_id")
|
|
182
187
|
.notNull()
|
|
183
188
|
.references(() => workLoops.id, { onDelete: "cascade" }),
|
|
184
189
|
triggerId: text("trigger_id").references(() => workLoopTriggers.id, { onDelete: "set null" }),
|
|
@@ -522,6 +527,25 @@ export const pluginRuns = pgTable("plugin_runs", {
|
|
|
522
527
|
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
523
528
|
});
|
|
524
529
|
|
|
530
|
+
export const pluginInstalls = pgTable("plugin_installs", {
|
|
531
|
+
id: text("id").primaryKey(),
|
|
532
|
+
companyId: text("company_id")
|
|
533
|
+
.notNull()
|
|
534
|
+
.references(() => companies.id, { onDelete: "cascade" }),
|
|
535
|
+
pluginId: text("plugin_id")
|
|
536
|
+
.notNull()
|
|
537
|
+
.references(() => plugins.id, { onDelete: "cascade" }),
|
|
538
|
+
pluginVersion: text("plugin_version").notNull(),
|
|
539
|
+
sourceType: text("source_type").notNull().default("registry"),
|
|
540
|
+
sourceRef: text("source_ref"),
|
|
541
|
+
integrity: text("integrity"),
|
|
542
|
+
buildHash: text("build_hash"),
|
|
543
|
+
artifactPath: text("artifact_path"),
|
|
544
|
+
manifestJson: text("manifest_json").notNull().default("{}"),
|
|
545
|
+
status: text("status").notNull().default("active"),
|
|
546
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
547
|
+
});
|
|
548
|
+
|
|
525
549
|
export const agentIssueLabels = pgTable(
|
|
526
550
|
"agent_issue_labels",
|
|
527
551
|
{
|
|
@@ -560,6 +584,7 @@ export const schema = {
|
|
|
560
584
|
plugins,
|
|
561
585
|
pluginConfigs,
|
|
562
586
|
pluginRuns,
|
|
587
|
+
pluginInstalls,
|
|
563
588
|
templates,
|
|
564
589
|
templateVersions,
|
|
565
590
|
templateInstalls,
|