bopodev-api 0.1.28 → 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.
Files changed (42) hide show
  1. package/package.json +4 -4
  2. package/src/app.ts +17 -69
  3. package/src/lib/run-artifact-paths.ts +8 -0
  4. package/src/middleware/cors-config.ts +36 -0
  5. package/src/middleware/request-actor.ts +10 -16
  6. package/src/middleware/request-id.ts +9 -0
  7. package/src/middleware/request-logging.ts +24 -0
  8. package/src/routes/agents.ts +3 -9
  9. package/src/routes/companies.ts +18 -1
  10. package/src/routes/goals.ts +7 -13
  11. package/src/routes/governance.ts +2 -5
  12. package/src/routes/heartbeats.ts +7 -25
  13. package/src/routes/issues.ts +62 -120
  14. package/src/routes/observability.ts +6 -1
  15. package/src/routes/plugins.ts +5 -17
  16. package/src/routes/projects.ts +7 -25
  17. package/src/routes/templates.ts +6 -21
  18. package/src/scripts/onboard-seed.ts +5 -7
  19. package/src/server.ts +33 -292
  20. package/src/services/company-export-service.ts +63 -0
  21. package/src/services/governance-service.ts +4 -1
  22. package/src/services/heartbeat-service/active-runs.ts +15 -0
  23. package/src/services/heartbeat-service/budget-override.ts +46 -0
  24. package/src/services/heartbeat-service/claims.ts +61 -0
  25. package/src/services/heartbeat-service/cron.ts +58 -0
  26. package/src/services/heartbeat-service/heartbeat-realtime.ts +28 -0
  27. package/src/services/heartbeat-service/heartbeat-run-summary-text.ts +53 -0
  28. package/src/services/{heartbeat-service.ts → heartbeat-service/heartbeat-run.ts} +183 -633
  29. package/src/services/heartbeat-service/index.ts +5 -0
  30. package/src/services/heartbeat-service/stop.ts +90 -0
  31. package/src/services/heartbeat-service/sweep.ts +145 -0
  32. package/src/services/heartbeat-service/types.ts +65 -0
  33. package/src/services/memory-file-service.ts +10 -2
  34. package/src/shutdown/graceful-shutdown.ts +77 -0
  35. package/src/startup/database.ts +41 -0
  36. package/src/startup/deployment-validation.ts +37 -0
  37. package/src/startup/env.ts +17 -0
  38. package/src/startup/runtime-health.ts +128 -0
  39. package/src/startup/scheduler-config.ts +39 -0
  40. package/src/types/express.d.ts +13 -0
  41. package/src/types/request-actor.ts +6 -0
  42. package/src/validation/issue-routes.ts +79 -0
@@ -0,0 +1,39 @@
1
+ import { asc, companies, eq } from "bopodev-db";
2
+ import type { BootstrappedDb } from "./database";
3
+
4
+ export async function resolveSchedulerCompanyId(
5
+ db: BootstrappedDb["db"],
6
+ configuredCompanyId: string | null
7
+ ) {
8
+ if (configuredCompanyId) {
9
+ const configured = await db
10
+ .select({ id: companies.id })
11
+ .from(companies)
12
+ .where(eq(companies.id, configuredCompanyId))
13
+ .limit(1);
14
+ if (configured.length > 0) {
15
+ return configuredCompanyId;
16
+ }
17
+ // eslint-disable-next-line no-console
18
+ console.warn(`[startup] BOPO_DEFAULT_COMPANY_ID='${configuredCompanyId}' was not found; using first available company.`);
19
+ }
20
+
21
+ const fallback = await db
22
+ .select({ id: companies.id })
23
+ .from(companies)
24
+ .orderBy(asc(companies.createdAt))
25
+ .limit(1);
26
+ const id = fallback[0]?.id;
27
+ return typeof id === "string" && id.length > 0 ? id : null;
28
+ }
29
+
30
+ export function shouldStartScheduler() {
31
+ const rawRole = (process.env.BOPO_SCHEDULER_ROLE ?? "auto").trim().toLowerCase();
32
+ if (rawRole === "off" || rawRole === "follower") {
33
+ return false;
34
+ }
35
+ if (rawRole === "leader" || rawRole === "auto") {
36
+ return true;
37
+ }
38
+ throw new Error(`Invalid BOPO_SCHEDULER_ROLE '${rawRole}'. Expected one of: auto, leader, follower, off.`);
39
+ }
@@ -0,0 +1,13 @@
1
+ import type { RequestActor } from "./request-actor";
2
+
3
+ declare global {
4
+ namespace Express {
5
+ interface Request {
6
+ actor?: RequestActor;
7
+ companyId?: string;
8
+ requestId?: string;
9
+ }
10
+ }
11
+ }
12
+
13
+ export {};
@@ -0,0 +1,6 @@
1
+ export type RequestActor = {
2
+ type: "board" | "member" | "agent";
3
+ id: string;
4
+ companyIds: string[] | null;
5
+ permissions: string[];
6
+ };
@@ -0,0 +1,79 @@
1
+ import { z } from "zod";
2
+
3
+ export const createIssueSchema = z.object({
4
+ projectId: z.string().min(1),
5
+ parentIssueId: z.string().optional(),
6
+ title: z.string().min(1),
7
+ body: z.string().optional(),
8
+ metadata: z
9
+ .object({
10
+ delegatedHiringIntent: z
11
+ .object({
12
+ intentType: z.literal("agent_hiring_request"),
13
+ requestedRole: z.string().nullable().optional(),
14
+ requestedRoleKey: z.string().nullable().optional(),
15
+ requestedTitle: z.string().nullable().optional(),
16
+ requestedName: z.string().nullable().optional(),
17
+ requestedManagerAgentId: z.string().nullable().optional(),
18
+ requestedProviderType: z.string().nullable().optional(),
19
+ requestedRuntimeModel: z.string().nullable().optional()
20
+ })
21
+ .optional()
22
+ })
23
+ .optional(),
24
+ status: z.enum(["todo", "in_progress", "blocked", "in_review", "done", "canceled"]).default("todo"),
25
+ priority: z.enum(["none", "low", "medium", "high", "urgent"]).default("none"),
26
+ assigneeAgentId: z.string().nullable().optional(),
27
+ goalIds: z.array(z.string().min(1)).default([]),
28
+ externalLink: z.string().max(2048).nullable().optional(),
29
+ labels: z.array(z.string()).default([]),
30
+ tags: z.array(z.string()).default([])
31
+ });
32
+
33
+ export const createIssueCommentSchema = z.object({
34
+ body: z.string().min(1),
35
+ recipients: z
36
+ .array(
37
+ z.object({
38
+ recipientType: z.enum(["agent", "board", "member"]),
39
+ recipientId: z.string().nullable().optional()
40
+ })
41
+ )
42
+ .default([]),
43
+ authorType: z.enum(["human", "agent", "system"]).optional(),
44
+ authorId: z.string().optional()
45
+ });
46
+
47
+ export const createIssueCommentLegacySchema = z.object({
48
+ issueId: z.string().min(1),
49
+ body: z.string().min(1),
50
+ recipients: z
51
+ .array(
52
+ z.object({
53
+ recipientType: z.enum(["agent", "board", "member"]),
54
+ recipientId: z.string().nullable().optional()
55
+ })
56
+ )
57
+ .default([]),
58
+ authorType: z.enum(["human", "agent", "system"]).optional(),
59
+ authorId: z.string().optional()
60
+ });
61
+
62
+ export const updateIssueCommentSchema = z.object({
63
+ body: z.string().min(1)
64
+ });
65
+
66
+ export const updateIssueSchema = z
67
+ .object({
68
+ projectId: z.string().min(1).optional(),
69
+ title: z.string().min(1).optional(),
70
+ body: z.string().nullable().optional(),
71
+ status: z.enum(["todo", "in_progress", "blocked", "in_review", "done", "canceled"]).optional(),
72
+ priority: z.enum(["none", "low", "medium", "high", "urgent"]).optional(),
73
+ assigneeAgentId: z.string().nullable().optional(),
74
+ goalIds: z.array(z.string().min(1)).optional(),
75
+ externalLink: z.string().max(2048).nullable().optional(),
76
+ labels: z.array(z.string()).optional(),
77
+ tags: z.array(z.string()).optional()
78
+ })
79
+ .refine((payload) => Object.keys(payload).length > 0, "At least one field must be provided.");