bopodev-api 0.1.12 → 0.1.14
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/LICENSE +1 -1
- package/package.json +6 -4
- package/src/app.ts +2 -0
- package/src/lib/agent-config.ts +36 -1
- package/src/lib/instance-paths.ts +12 -0
- package/src/lib/opencode-model.ts +11 -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 +111 -2
- package/src/routes/companies.ts +4 -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 +595 -18
- package/src/routes/plugins.ts +257 -0
- package/src/scripts/onboard-seed.ts +60 -12
- package/src/server.ts +62 -3
- package/src/services/governance-service.ts +106 -23
- package/src/services/heartbeat-service.ts +750 -49
- package/src/services/memory-file-service.ts +249 -0
- package/src/services/model-pricing.ts +217 -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,24 +10,31 @@ 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,
|
|
21
|
+
resolveRuntimeModelForProvider,
|
|
19
22
|
requiresRuntimeCwd,
|
|
20
23
|
runtimeConfigToDb,
|
|
21
24
|
runtimeConfigToStateBlobPatch
|
|
22
25
|
} from "../lib/agent-config";
|
|
26
|
+
import { resolveOpencodeRuntimeModel } from "../lib/opencode-model";
|
|
23
27
|
import { hasText, resolveDefaultRuntimeCwdForCompany } from "../lib/workspace-policy";
|
|
28
|
+
import { appendDurableFact } from "./memory-file-service";
|
|
24
29
|
|
|
25
30
|
const approvalGatedActions = new Set([
|
|
26
31
|
"hire_agent",
|
|
27
32
|
"activate_goal",
|
|
28
33
|
"override_budget",
|
|
29
34
|
"pause_agent",
|
|
30
|
-
"terminate_agent"
|
|
35
|
+
"terminate_agent",
|
|
36
|
+
"promote_memory_fact",
|
|
37
|
+
"grant_plugin_capabilities"
|
|
31
38
|
]);
|
|
32
39
|
|
|
33
40
|
const hireAgentPayloadSchema = AgentCreateRequestSchema.extend({
|
|
@@ -56,6 +63,18 @@ const activateGoalPayloadSchema = z.object({
|
|
|
56
63
|
title: z.string().min(1),
|
|
57
64
|
description: z.string().optional()
|
|
58
65
|
});
|
|
66
|
+
const promoteMemoryFactPayloadSchema = z.object({
|
|
67
|
+
agentId: z.string().min(1),
|
|
68
|
+
fact: z.string().min(1),
|
|
69
|
+
sourceRunId: z.string().optional()
|
|
70
|
+
});
|
|
71
|
+
const grantPluginCapabilitiesPayloadSchema = z.object({
|
|
72
|
+
pluginId: z.string().min(1),
|
|
73
|
+
enabled: z.boolean().optional(),
|
|
74
|
+
priority: z.number().int().min(0).max(1000).optional(),
|
|
75
|
+
grantedCapabilities: z.array(z.string().min(1)).default([]),
|
|
76
|
+
config: z.record(z.string(), z.unknown()).default({})
|
|
77
|
+
});
|
|
59
78
|
const AGENT_STARTUP_PROJECT_NAME = "Agent Onboarding";
|
|
60
79
|
const AGENT_STARTUP_TASK_MARKER = "[bopodev:onboarding:agent-startup:v1]";
|
|
61
80
|
|
|
@@ -102,7 +121,7 @@ export async function resolveApproval(
|
|
|
102
121
|
let execution:
|
|
103
122
|
| {
|
|
104
123
|
applied: boolean;
|
|
105
|
-
entityType?: "agent" | "goal";
|
|
124
|
+
entityType?: "agent" | "goal" | "memory";
|
|
106
125
|
entityId?: string;
|
|
107
126
|
entity?: Record<string, unknown>;
|
|
108
127
|
}
|
|
@@ -176,12 +195,32 @@ async function applyApprovalAction(db: BopoDb, companyId: string, action: string
|
|
|
176
195
|
},
|
|
177
196
|
defaultRuntimeCwd
|
|
178
197
|
});
|
|
198
|
+
runtimeConfig.runtimeModel = await resolveOpencodeRuntimeModel(parsed.data.providerType, runtimeConfig);
|
|
199
|
+
runtimeConfig.runtimeModel = resolveRuntimeModelForProvider(parsed.data.providerType, runtimeConfig.runtimeModel);
|
|
200
|
+
if (providerRequiresNamedModel(parsed.data.providerType) && !hasText(runtimeConfig.runtimeModel)) {
|
|
201
|
+
throw new GovernanceError("Approval payload for agent hiring must include a named runtime model.");
|
|
202
|
+
}
|
|
179
203
|
if (requiresRuntimeCwd(parsed.data.providerType) && !hasText(runtimeConfig.runtimeCwd)) {
|
|
180
204
|
throw new GovernanceError("Approval payload for agent hiring is missing runtime working directory.");
|
|
181
205
|
}
|
|
182
206
|
if (requiresRuntimeCwd(parsed.data.providerType) && hasText(runtimeConfig.runtimeCwd)) {
|
|
183
207
|
await mkdir(runtimeConfig.runtimeCwd!, { recursive: true });
|
|
184
208
|
}
|
|
209
|
+
const existingAgents = await listAgents(db, companyId);
|
|
210
|
+
const duplicate = existingAgents.find(
|
|
211
|
+
(agent) =>
|
|
212
|
+
agent.role === parsed.data.role &&
|
|
213
|
+
(agent.managerAgentId ?? null) === (parsed.data.managerAgentId ?? null) &&
|
|
214
|
+
agent.status !== "terminated"
|
|
215
|
+
);
|
|
216
|
+
if (duplicate) {
|
|
217
|
+
return {
|
|
218
|
+
applied: false,
|
|
219
|
+
entityType: "agent" as const,
|
|
220
|
+
entityId: duplicate.id,
|
|
221
|
+
entity: duplicate as unknown as Record<string, unknown>
|
|
222
|
+
};
|
|
223
|
+
}
|
|
185
224
|
|
|
186
225
|
const agent = await createAgent(db, {
|
|
187
226
|
companyId,
|
|
@@ -196,7 +235,7 @@ async function applyApprovalAction(db: BopoDb, companyId: string, action: string
|
|
|
196
235
|
initialState: runtimeConfigToStateBlobPatch(runtimeConfig)
|
|
197
236
|
});
|
|
198
237
|
const startupProjectId = await ensureAgentStartupProject(db, companyId);
|
|
199
|
-
await ensureAgentStartupIssue(db, companyId, startupProjectId, agent.id, agent.role
|
|
238
|
+
await ensureAgentStartupIssue(db, companyId, startupProjectId, agent.id, agent.role);
|
|
200
239
|
|
|
201
240
|
return {
|
|
202
241
|
applied: true,
|
|
@@ -262,9 +301,62 @@ async function applyApprovalAction(db: BopoDb, companyId: string, action: string
|
|
|
262
301
|
return { applied: false };
|
|
263
302
|
}
|
|
264
303
|
|
|
304
|
+
if (action === "promote_memory_fact") {
|
|
305
|
+
const parsed = promoteMemoryFactPayloadSchema.safeParse(payload);
|
|
306
|
+
if (!parsed.success) {
|
|
307
|
+
throw new GovernanceError("Approval payload for memory promotion is invalid.");
|
|
308
|
+
}
|
|
309
|
+
const targetFile = await appendDurableFact({
|
|
310
|
+
companyId,
|
|
311
|
+
agentId: parsed.data.agentId,
|
|
312
|
+
fact: parsed.data.fact,
|
|
313
|
+
sourceRunId: parsed.data.sourceRunId ?? null
|
|
314
|
+
});
|
|
315
|
+
return {
|
|
316
|
+
applied: Boolean(targetFile),
|
|
317
|
+
entityType: "memory" as const,
|
|
318
|
+
entityId: parsed.data.agentId,
|
|
319
|
+
entity: {
|
|
320
|
+
agentId: parsed.data.agentId,
|
|
321
|
+
sourceRunId: parsed.data.sourceRunId ?? null,
|
|
322
|
+
fact: parsed.data.fact,
|
|
323
|
+
targetFile
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
if (action === "grant_plugin_capabilities") {
|
|
328
|
+
const parsed = grantPluginCapabilitiesPayloadSchema.safeParse(payload);
|
|
329
|
+
if (!parsed.success) {
|
|
330
|
+
throw new GovernanceError("Approval payload for plugin capability grant is invalid.");
|
|
331
|
+
}
|
|
332
|
+
await updatePluginConfig(db, {
|
|
333
|
+
companyId,
|
|
334
|
+
pluginId: parsed.data.pluginId,
|
|
335
|
+
enabled: parsed.data.enabled,
|
|
336
|
+
priority: parsed.data.priority,
|
|
337
|
+
grantedCapabilitiesJson: JSON.stringify(parsed.data.grantedCapabilities),
|
|
338
|
+
configJson: JSON.stringify(parsed.data.config)
|
|
339
|
+
});
|
|
340
|
+
return {
|
|
341
|
+
applied: true,
|
|
342
|
+
entityType: "memory" as const,
|
|
343
|
+
entityId: parsed.data.pluginId,
|
|
344
|
+
entity: {
|
|
345
|
+
pluginId: parsed.data.pluginId,
|
|
346
|
+
enabled: parsed.data.enabled ?? null,
|
|
347
|
+
priority: parsed.data.priority ?? null,
|
|
348
|
+
grantedCapabilities: parsed.data.grantedCapabilities
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
265
353
|
throw new GovernanceError(`Unsupported approval action: ${action}`);
|
|
266
354
|
}
|
|
267
355
|
|
|
356
|
+
function providerRequiresNamedModel(providerType: string) {
|
|
357
|
+
return providerType !== "http" && providerType !== "shell";
|
|
358
|
+
}
|
|
359
|
+
|
|
268
360
|
function parsePayload(payloadJson: string) {
|
|
269
361
|
try {
|
|
270
362
|
const parsed = JSON.parse(payloadJson) as unknown;
|
|
@@ -293,11 +385,10 @@ async function ensureAgentStartupIssue(
|
|
|
293
385
|
companyId: string,
|
|
294
386
|
projectId: string,
|
|
295
387
|
agentId: string,
|
|
296
|
-
role: string
|
|
297
|
-
name: string
|
|
388
|
+
role: string
|
|
298
389
|
) {
|
|
299
390
|
const title = `Set up ${role} operating files`;
|
|
300
|
-
const body = buildAgentStartupTaskBody(
|
|
391
|
+
const body = buildAgentStartupTaskBody(agentId);
|
|
301
392
|
const existingIssues = await listIssues(db, companyId);
|
|
302
393
|
const existing = existingIssues.find(
|
|
303
394
|
(issue) =>
|
|
@@ -323,20 +414,20 @@ async function ensureAgentStartupIssue(
|
|
|
323
414
|
return created.id;
|
|
324
415
|
}
|
|
325
416
|
|
|
326
|
-
function buildAgentStartupTaskBody(
|
|
327
|
-
const
|
|
417
|
+
function buildAgentStartupTaskBody(agentId: string) {
|
|
418
|
+
const agentFolder = `agents/${agentId}`;
|
|
328
419
|
return [
|
|
329
420
|
AGENT_STARTUP_TASK_MARKER,
|
|
330
421
|
"",
|
|
331
422
|
`Create your operating baseline before starting feature delivery work.`,
|
|
332
423
|
"",
|
|
333
|
-
`1. Create the folder
|
|
424
|
+
`1. Create the folder \`${agentFolder}/\` in the repository workspace.`,
|
|
334
425
|
"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
|
|
426
|
+
` - \`${agentFolder}/AGENTS.md\``,
|
|
427
|
+
` - \`${agentFolder}/HEARTBEAT.md\``,
|
|
428
|
+
` - \`${agentFolder}/SOUL.md\``,
|
|
429
|
+
` - \`${agentFolder}/TOOLS.md\``,
|
|
430
|
+
`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
431
|
"4. Post an issue comment summarizing completed setup artifacts.",
|
|
341
432
|
"",
|
|
342
433
|
"Safety checks:",
|
|
@@ -345,11 +436,3 @@ function buildAgentStartupTaskBody(agentName: string, agentId: string) {
|
|
|
345
436
|
].join("\n");
|
|
346
437
|
}
|
|
347
438
|
|
|
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
|
-
|