bopodev-api 0.1.11 → 0.1.13

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.
@@ -10,9 +10,11 @@ import {
10
10
  createIssue,
11
11
  createProject,
12
12
  goals,
13
+ listAgents,
13
14
  listIssues,
14
15
  listProjects,
15
- projects
16
+ projects,
17
+ updatePluginConfig
16
18
  } from "bopodev-db";
17
19
  import {
18
20
  normalizeRuntimeConfig,
@@ -20,14 +22,18 @@ import {
20
22
  runtimeConfigToDb,
21
23
  runtimeConfigToStateBlobPatch
22
24
  } from "../lib/agent-config";
25
+ import { resolveOpencodeRuntimeModel } from "../lib/opencode-model";
23
26
  import { hasText, resolveDefaultRuntimeCwdForCompany } from "../lib/workspace-policy";
27
+ import { appendDurableFact } from "./memory-file-service";
24
28
 
25
29
  const approvalGatedActions = new Set([
26
30
  "hire_agent",
27
31
  "activate_goal",
28
32
  "override_budget",
29
33
  "pause_agent",
30
- "terminate_agent"
34
+ "terminate_agent",
35
+ "promote_memory_fact",
36
+ "grant_plugin_capabilities"
31
37
  ]);
32
38
 
33
39
  const hireAgentPayloadSchema = AgentCreateRequestSchema.extend({
@@ -56,6 +62,18 @@ const activateGoalPayloadSchema = z.object({
56
62
  title: z.string().min(1),
57
63
  description: z.string().optional()
58
64
  });
65
+ const promoteMemoryFactPayloadSchema = z.object({
66
+ agentId: z.string().min(1),
67
+ fact: z.string().min(1),
68
+ sourceRunId: z.string().optional()
69
+ });
70
+ const grantPluginCapabilitiesPayloadSchema = z.object({
71
+ pluginId: z.string().min(1),
72
+ enabled: z.boolean().optional(),
73
+ priority: z.number().int().min(0).max(1000).optional(),
74
+ grantedCapabilities: z.array(z.string().min(1)).default([]),
75
+ config: z.record(z.string(), z.unknown()).default({})
76
+ });
59
77
  const AGENT_STARTUP_PROJECT_NAME = "Agent Onboarding";
60
78
  const AGENT_STARTUP_TASK_MARKER = "[bopodev:onboarding:agent-startup:v1]";
61
79
 
@@ -102,7 +120,7 @@ export async function resolveApproval(
102
120
  let execution:
103
121
  | {
104
122
  applied: boolean;
105
- entityType?: "agent" | "goal";
123
+ entityType?: "agent" | "goal" | "memory";
106
124
  entityId?: string;
107
125
  entity?: Record<string, unknown>;
108
126
  }
@@ -176,12 +194,28 @@ async function applyApprovalAction(db: BopoDb, companyId: string, action: string
176
194
  },
177
195
  defaultRuntimeCwd
178
196
  });
197
+ runtimeConfig.runtimeModel = await resolveOpencodeRuntimeModel(parsed.data.providerType, runtimeConfig);
179
198
  if (requiresRuntimeCwd(parsed.data.providerType) && !hasText(runtimeConfig.runtimeCwd)) {
180
199
  throw new GovernanceError("Approval payload for agent hiring is missing runtime working directory.");
181
200
  }
182
201
  if (requiresRuntimeCwd(parsed.data.providerType) && hasText(runtimeConfig.runtimeCwd)) {
183
202
  await mkdir(runtimeConfig.runtimeCwd!, { recursive: true });
184
203
  }
204
+ const existingAgents = await listAgents(db, companyId);
205
+ const duplicate = existingAgents.find(
206
+ (agent) =>
207
+ agent.role === parsed.data.role &&
208
+ (agent.managerAgentId ?? null) === (parsed.data.managerAgentId ?? null) &&
209
+ agent.status !== "terminated"
210
+ );
211
+ if (duplicate) {
212
+ return {
213
+ applied: false,
214
+ entityType: "agent" as const,
215
+ entityId: duplicate.id,
216
+ entity: duplicate as unknown as Record<string, unknown>
217
+ };
218
+ }
185
219
 
186
220
  const agent = await createAgent(db, {
187
221
  companyId,
@@ -196,7 +230,7 @@ async function applyApprovalAction(db: BopoDb, companyId: string, action: string
196
230
  initialState: runtimeConfigToStateBlobPatch(runtimeConfig)
197
231
  });
198
232
  const startupProjectId = await ensureAgentStartupProject(db, companyId);
199
- await ensureAgentStartupIssue(db, companyId, startupProjectId, agent.id, agent.role, agent.name);
233
+ await ensureAgentStartupIssue(db, companyId, startupProjectId, agent.id, agent.role);
200
234
 
201
235
  return {
202
236
  applied: true,
@@ -262,6 +296,55 @@ async function applyApprovalAction(db: BopoDb, companyId: string, action: string
262
296
  return { applied: false };
263
297
  }
264
298
 
299
+ if (action === "promote_memory_fact") {
300
+ const parsed = promoteMemoryFactPayloadSchema.safeParse(payload);
301
+ if (!parsed.success) {
302
+ throw new GovernanceError("Approval payload for memory promotion is invalid.");
303
+ }
304
+ const targetFile = await appendDurableFact({
305
+ companyId,
306
+ agentId: parsed.data.agentId,
307
+ fact: parsed.data.fact,
308
+ sourceRunId: parsed.data.sourceRunId ?? null
309
+ });
310
+ return {
311
+ applied: Boolean(targetFile),
312
+ entityType: "memory" as const,
313
+ entityId: parsed.data.agentId,
314
+ entity: {
315
+ agentId: parsed.data.agentId,
316
+ sourceRunId: parsed.data.sourceRunId ?? null,
317
+ fact: parsed.data.fact,
318
+ targetFile
319
+ }
320
+ };
321
+ }
322
+ if (action === "grant_plugin_capabilities") {
323
+ const parsed = grantPluginCapabilitiesPayloadSchema.safeParse(payload);
324
+ if (!parsed.success) {
325
+ throw new GovernanceError("Approval payload for plugin capability grant is invalid.");
326
+ }
327
+ await updatePluginConfig(db, {
328
+ companyId,
329
+ pluginId: parsed.data.pluginId,
330
+ enabled: parsed.data.enabled,
331
+ priority: parsed.data.priority,
332
+ grantedCapabilitiesJson: JSON.stringify(parsed.data.grantedCapabilities),
333
+ configJson: JSON.stringify(parsed.data.config)
334
+ });
335
+ return {
336
+ applied: true,
337
+ entityType: "memory" as const,
338
+ entityId: parsed.data.pluginId,
339
+ entity: {
340
+ pluginId: parsed.data.pluginId,
341
+ enabled: parsed.data.enabled ?? null,
342
+ priority: parsed.data.priority ?? null,
343
+ grantedCapabilities: parsed.data.grantedCapabilities
344
+ }
345
+ };
346
+ }
347
+
265
348
  throw new GovernanceError(`Unsupported approval action: ${action}`);
266
349
  }
267
350
 
@@ -293,11 +376,10 @@ async function ensureAgentStartupIssue(
293
376
  companyId: string,
294
377
  projectId: string,
295
378
  agentId: string,
296
- role: string,
297
- name: string
379
+ role: string
298
380
  ) {
299
381
  const title = `Set up ${role} operating files`;
300
- const body = buildAgentStartupTaskBody(name, agentId);
382
+ const body = buildAgentStartupTaskBody(agentId);
301
383
  const existingIssues = await listIssues(db, companyId);
302
384
  const existing = existingIssues.find(
303
385
  (issue) =>
@@ -323,20 +405,20 @@ async function ensureAgentStartupIssue(
323
405
  return created.id;
324
406
  }
325
407
 
326
- function buildAgentStartupTaskBody(agentName: string, agentId: string) {
327
- const slug = slugifyAgentPath(agentName || agentId);
408
+ function buildAgentStartupTaskBody(agentId: string) {
409
+ const agentFolder = `agents/${agentId}`;
328
410
  return [
329
411
  AGENT_STARTUP_TASK_MARKER,
330
412
  "",
331
413
  `Create your operating baseline before starting feature delivery work.`,
332
414
  "",
333
- `1. Create the folder \`agents/${slug}/\` in the repository workspace.`,
415
+ `1. Create the folder \`${agentFolder}/\` in the repository workspace.`,
334
416
  "2. Author these files with your own responsibilities and working style:",
335
- ` - \`agents/${slug}/AGENTS.md\``,
336
- ` - \`agents/${slug}/HEARTBEAT.md\``,
337
- ` - \`agents/${slug}/SOUL.md\``,
338
- ` - \`agents/${slug}/TOOLS.md\``,
339
- `3. Update your own agent runtime config via \`PUT /agents/:agentId\` and set \`runtimeConfig.bootstrapPrompt\` to reference \`agents/${slug}/AGENTS.md\` as your primary guide.`,
417
+ ` - \`${agentFolder}/AGENTS.md\``,
418
+ ` - \`${agentFolder}/HEARTBEAT.md\``,
419
+ ` - \`${agentFolder}/SOUL.md\``,
420
+ ` - \`${agentFolder}/TOOLS.md\``,
421
+ `3. Update your own agent runtime config via \`PUT /agents/:agentId\` and set \`runtimeConfig.bootstrapPrompt\` to reference \`${agentFolder}/AGENTS.md\` as your primary guide.`,
340
422
  "4. Post an issue comment summarizing completed setup artifacts.",
341
423
  "",
342
424
  "Safety checks:",
@@ -345,11 +427,3 @@ function buildAgentStartupTaskBody(agentName: string, agentId: string) {
345
427
  ].join("\n");
346
428
  }
347
429
 
348
- function slugifyAgentPath(value: string) {
349
- const base = value
350
- .toLowerCase()
351
- .replace(/[^a-z0-9]+/g, "-")
352
- .replace(/^-+|-+$/g, "");
353
- return base || "agent";
354
- }
355
-