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.
- package/package.json +6 -4
- package/src/app.ts +2 -0
- package/src/lib/instance-paths.ts +12 -0
- package/src/lib/opencode-model.ts +35 -0
- package/src/lib/workspace-policy.ts +5 -0
- package/src/realtime/heartbeat-runs.ts +78 -0
- package/src/realtime/hub.ts +37 -1
- package/src/realtime/office-space.ts +10 -1
- package/src/routes/agents.ts +89 -2
- package/src/routes/companies.ts +2 -0
- package/src/routes/governance.ts +9 -2
- package/src/routes/heartbeats.ts +2 -1
- package/src/routes/issues.ts +321 -0
- package/src/routes/observability.ts +546 -18
- package/src/routes/plugins.ts +257 -0
- package/src/scripts/onboard-seed.ts +57 -12
- package/src/server.ts +62 -3
- package/src/services/governance-service.ts +97 -23
- package/src/services/heartbeat-service.ts +633 -31
- package/src/services/memory-file-service.ts +249 -0
- package/src/services/plugin-manifest-loader.ts +65 -0
- package/src/services/plugin-runtime.ts +580 -0
- package/src/services/plugin-webhook-executor.ts +94 -0
|
@@ -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
|
|
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(
|
|
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(
|
|
327
|
-
const
|
|
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
|
|
415
|
+
`1. Create the folder \`${agentFolder}/\` in the repository workspace.`,
|
|
334
416
|
"2. Author these files with your own responsibilities and working style:",
|
|
335
|
-
` -
|
|
336
|
-
` -
|
|
337
|
-
` -
|
|
338
|
-
` -
|
|
339
|
-
`3. Update your own agent runtime config via \`PUT /agents/:agentId\` and set \`runtimeConfig.bootstrapPrompt\` to reference
|
|
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
|
-
|