bopodev-api 0.1.29 → 0.1.30
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 +4 -4
- package/src/lib/ceo-bootstrap-prompt.ts +1 -0
- package/src/realtime/office-space.ts +1 -0
- package/src/routes/agents.ts +87 -37
- package/src/routes/companies.ts +2 -0
- package/src/routes/issues.ts +3 -0
- package/src/scripts/onboard-seed.ts +13 -1
- package/src/services/governance-service.ts +6 -13
- package/src/services/heartbeat-service/heartbeat-run.ts +18 -1
- package/src/services/heartbeat-service/types.ts +1 -0
- package/src/services/template-apply-service.ts +6 -0
- package/src/services/template-catalog.ts +37 -3
- package/src/validation/issue-routes.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bopodev-api",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
"nanoid": "^5.1.5",
|
|
18
18
|
"ws": "^8.19.0",
|
|
19
19
|
"zod": "^4.1.5",
|
|
20
|
-
"bopodev-contracts": "0.1.
|
|
21
|
-
"bopodev-
|
|
22
|
-
"bopodev-
|
|
20
|
+
"bopodev-contracts": "0.1.30",
|
|
21
|
+
"bopodev-db": "0.1.30",
|
|
22
|
+
"bopodev-agent-sdk": "0.1.30"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/cors": "^2.8.19",
|
|
@@ -5,6 +5,7 @@ export function buildDefaultCeoBootstrapPrompt() {
|
|
|
5
5
|
"- Clarify missing constraints before hiring when requirements are ambiguous.",
|
|
6
6
|
"- Choose reporting lines, provider, model, and permissions that fit company goals and budget.",
|
|
7
7
|
"- Use governance-safe hiring via `POST /agents` with `requestApproval: true` unless explicitly told otherwise.",
|
|
8
|
+
"- Always set `capabilities` on every new hire: one or two sentences describing what they do, for the org chart and team roster (delegation). If the issue metadata or body specifies requested capabilities, use or refine that text; if missing, write an appropriate line from the role and request details.",
|
|
8
9
|
"- Avoid duplicate hires by checking existing agents and pending approvals first.",
|
|
9
10
|
"- Use the control-plane coordination skill as the source of truth for endpoint paths, required headers, and approval workflow steps.",
|
|
10
11
|
"- Record hiring rationale and key decisions in issue comments for auditability."
|
|
@@ -437,6 +437,7 @@ function normalizeProviderType(value: string): OfficeOccupant["providerType"] {
|
|
|
437
437
|
value === "gemini_cli" ||
|
|
438
438
|
value === "openai_api" ||
|
|
439
439
|
value === "anthropic_api" ||
|
|
440
|
+
value === "openclaw_gateway" ||
|
|
440
441
|
value === "http" ||
|
|
441
442
|
value === "shell"
|
|
442
443
|
? value
|
package/src/routes/agents.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Router } from "express";
|
|
1
|
+
import { Router, type Response } from "express";
|
|
2
2
|
import { mkdir } from "node:fs/promises";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import {
|
|
@@ -82,17 +82,25 @@ const runtimePreflightSchema = z.object({
|
|
|
82
82
|
"gemini_cli",
|
|
83
83
|
"openai_api",
|
|
84
84
|
"anthropic_api",
|
|
85
|
+
"openclaw_gateway",
|
|
85
86
|
"http",
|
|
86
87
|
"shell"
|
|
87
88
|
]),
|
|
88
89
|
runtimeConfig: z.record(z.string(), z.unknown()).optional(),
|
|
89
90
|
...legacyRuntimeConfigSchema.shape
|
|
90
91
|
});
|
|
92
|
+
|
|
93
|
+
/** Body for POST /agents/adapter-models/:providerType (runtime for CLI discovery). */
|
|
94
|
+
const adapterModelsBodySchema = z.object({
|
|
95
|
+
runtimeConfig: z.record(z.string(), z.unknown()).optional(),
|
|
96
|
+
...legacyRuntimeConfigSchema.shape
|
|
97
|
+
});
|
|
91
98
|
const UPDATE_AGENT_ALLOWED_KEYS = new Set([
|
|
92
99
|
"managerAgentId",
|
|
93
100
|
"role",
|
|
94
101
|
"roleKey",
|
|
95
102
|
"title",
|
|
103
|
+
"capabilities",
|
|
96
104
|
"name",
|
|
97
105
|
"providerType",
|
|
98
106
|
"status",
|
|
@@ -135,7 +143,7 @@ function toAgentResponse(agent: Record<string, unknown>) {
|
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
function providerRequiresNamedModel(providerType: string) {
|
|
138
|
-
return providerType !== "http" && providerType !== "shell";
|
|
146
|
+
return providerType !== "http" && providerType !== "shell" && providerType !== "openclaw_gateway";
|
|
139
147
|
}
|
|
140
148
|
|
|
141
149
|
const agentResponseSchema = AgentSchema.extend({
|
|
@@ -149,6 +157,66 @@ function ensureNamedRuntimeModel(providerType: string, runtimeModel: string | un
|
|
|
149
157
|
return hasText(runtimeModel);
|
|
150
158
|
}
|
|
151
159
|
|
|
160
|
+
type AdapterModelsProviderType = NonNullable<z.infer<typeof runtimePreflightSchema>["providerType"]>;
|
|
161
|
+
|
|
162
|
+
async function handleAdapterModelsRequest(
|
|
163
|
+
ctx: AppContext,
|
|
164
|
+
res: Response,
|
|
165
|
+
companyId: string,
|
|
166
|
+
providerType: string,
|
|
167
|
+
parsedBody: z.infer<typeof adapterModelsBodySchema> | null
|
|
168
|
+
) {
|
|
169
|
+
if (!runtimePreflightSchema.shape.providerType.safeParse(providerType).success) {
|
|
170
|
+
return sendError(res, `Unsupported provider type: ${providerType}`, 422);
|
|
171
|
+
}
|
|
172
|
+
const defaultRuntimeCwd = await resolveDefaultRuntimeCwdForCompany(ctx.db, companyId);
|
|
173
|
+
let runtimeConfig: ReturnType<typeof normalizeRuntimeConfig>;
|
|
174
|
+
try {
|
|
175
|
+
if (parsedBody) {
|
|
176
|
+
runtimeConfig = normalizeRuntimeConfig({
|
|
177
|
+
runtimeConfig: parsedBody.runtimeConfig,
|
|
178
|
+
legacy: {
|
|
179
|
+
runtimeCommand: parsedBody.runtimeCommand,
|
|
180
|
+
runtimeArgs: parsedBody.runtimeArgs,
|
|
181
|
+
runtimeCwd: parsedBody.runtimeCwd,
|
|
182
|
+
runtimeTimeoutMs: parsedBody.runtimeTimeoutMs,
|
|
183
|
+
runtimeModel: parsedBody.runtimeModel,
|
|
184
|
+
runtimeThinkingEffort: parsedBody.runtimeThinkingEffort,
|
|
185
|
+
bootstrapPrompt: parsedBody.bootstrapPrompt,
|
|
186
|
+
runtimeTimeoutSec: parsedBody.runtimeTimeoutSec,
|
|
187
|
+
interruptGraceSec: parsedBody.interruptGraceSec,
|
|
188
|
+
runtimeEnv: parsedBody.runtimeEnv,
|
|
189
|
+
runPolicy: parsedBody.runPolicy
|
|
190
|
+
},
|
|
191
|
+
defaultRuntimeCwd
|
|
192
|
+
});
|
|
193
|
+
} else {
|
|
194
|
+
runtimeConfig = normalizeRuntimeConfig({ defaultRuntimeCwd });
|
|
195
|
+
}
|
|
196
|
+
runtimeConfig = enforceRuntimeCwdPolicy(companyId, runtimeConfig);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return sendError(res, String(error), 422);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (parsedBody && runtimeConfig.runtimeCwd) {
|
|
202
|
+
await mkdir(runtimeConfig.runtimeCwd, { recursive: true });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const typedProviderType = providerType as AdapterModelsProviderType;
|
|
206
|
+
const models = await getAdapterModels(typedProviderType, {
|
|
207
|
+
command: runtimeConfig.runtimeCommand,
|
|
208
|
+
args: runtimeConfig.runtimeArgs,
|
|
209
|
+
cwd: runtimeConfig.runtimeCwd,
|
|
210
|
+
env: runtimeConfig.runtimeEnv,
|
|
211
|
+
model: runtimeConfig.runtimeModel,
|
|
212
|
+
thinkingEffort: runtimeConfig.runtimeThinkingEffort,
|
|
213
|
+
timeoutMs: runtimeConfig.runtimeTimeoutSec > 0 ? runtimeConfig.runtimeTimeoutSec * 1000 : undefined,
|
|
214
|
+
interruptGraceSec: runtimeConfig.interruptGraceSec,
|
|
215
|
+
runPolicy: runtimeConfig.runPolicy
|
|
216
|
+
});
|
|
217
|
+
return sendOk(res, { providerType: typedProviderType, models });
|
|
218
|
+
}
|
|
219
|
+
|
|
152
220
|
export function createAgentsRouter(ctx: AppContext) {
|
|
153
221
|
const router = Router();
|
|
154
222
|
router.use(requireCompanyScope);
|
|
@@ -229,42 +297,16 @@ export function createAgentsRouter(ctx: AppContext) {
|
|
|
229
297
|
|
|
230
298
|
router.get("/adapter-models/:providerType", async (req, res) => {
|
|
231
299
|
const providerType = req.params.providerType;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
defaultRuntimeCwd
|
|
241
|
-
});
|
|
242
|
-
runtimeConfig = enforceRuntimeCwdPolicy(req.companyId!, runtimeConfig);
|
|
243
|
-
} catch (error) {
|
|
244
|
-
return sendError(res, String(error), 422);
|
|
300
|
+
return handleAdapterModelsRequest(ctx, res, req.companyId!, providerType, null);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
router.post("/adapter-models/:providerType", async (req, res) => {
|
|
304
|
+
const providerType = req.params.providerType;
|
|
305
|
+
const parsed = adapterModelsBodySchema.safeParse(req.body ?? {});
|
|
306
|
+
if (!parsed.success) {
|
|
307
|
+
return sendError(res, parsed.error.message, 422);
|
|
245
308
|
}
|
|
246
|
-
|
|
247
|
-
| "claude_code"
|
|
248
|
-
| "codex"
|
|
249
|
-
| "cursor"
|
|
250
|
-
| "opencode"
|
|
251
|
-
| "gemini_cli"
|
|
252
|
-
| "openai_api"
|
|
253
|
-
| "anthropic_api"
|
|
254
|
-
| "http"
|
|
255
|
-
| "shell";
|
|
256
|
-
const models = await getAdapterModels(typedProviderType, {
|
|
257
|
-
command: runtimeConfig.runtimeCommand,
|
|
258
|
-
args: runtimeConfig.runtimeArgs,
|
|
259
|
-
cwd: runtimeConfig.runtimeCwd,
|
|
260
|
-
env: runtimeConfig.runtimeEnv,
|
|
261
|
-
model: runtimeConfig.runtimeModel,
|
|
262
|
-
thinkingEffort: runtimeConfig.runtimeThinkingEffort,
|
|
263
|
-
timeoutMs: runtimeConfig.runtimeTimeoutSec > 0 ? runtimeConfig.runtimeTimeoutSec * 1000 : undefined,
|
|
264
|
-
interruptGraceSec: runtimeConfig.interruptGraceSec,
|
|
265
|
-
runPolicy: runtimeConfig.runPolicy
|
|
266
|
-
});
|
|
267
|
-
return sendOk(res, { providerType: typedProviderType, models });
|
|
309
|
+
return handleAdapterModelsRequest(ctx, res, req.companyId!, providerType, parsed.data);
|
|
268
310
|
});
|
|
269
311
|
|
|
270
312
|
router.post("/runtime-preflight", async (req, res) => {
|
|
@@ -425,6 +467,7 @@ export function createAgentsRouter(ctx: AppContext) {
|
|
|
425
467
|
role: resolveAgentRoleText(parsed.data.role, parsed.data.roleKey, parsed.data.title),
|
|
426
468
|
roleKey: normalizeRoleKey(parsed.data.roleKey),
|
|
427
469
|
title: normalizeTitle(parsed.data.title),
|
|
470
|
+
capabilities: normalizeCapabilities(parsed.data.capabilities),
|
|
428
471
|
name: parsed.data.name,
|
|
429
472
|
providerType: parsed.data.providerType,
|
|
430
473
|
heartbeatCron: parsed.data.heartbeatCron,
|
|
@@ -573,6 +616,8 @@ export function createAgentsRouter(ctx: AppContext) {
|
|
|
573
616
|
: undefined,
|
|
574
617
|
roleKey: parsed.data.roleKey !== undefined ? normalizeRoleKey(parsed.data.roleKey) : undefined,
|
|
575
618
|
title: parsed.data.title !== undefined ? normalizeTitle(parsed.data.title) : undefined,
|
|
619
|
+
capabilities:
|
|
620
|
+
parsed.data.capabilities !== undefined ? normalizeCapabilities(parsed.data.capabilities) : undefined,
|
|
576
621
|
name: parsed.data.name,
|
|
577
622
|
providerType: parsed.data.providerType,
|
|
578
623
|
status: parsed.data.status,
|
|
@@ -823,6 +868,11 @@ function normalizeTitle(input: string | null | undefined) {
|
|
|
823
868
|
return normalized ? normalized : null;
|
|
824
869
|
}
|
|
825
870
|
|
|
871
|
+
function normalizeCapabilities(input: string | null | undefined) {
|
|
872
|
+
const normalized = input?.trim();
|
|
873
|
+
return normalized ? normalized : null;
|
|
874
|
+
}
|
|
875
|
+
|
|
826
876
|
function resolveAgentRoleText(
|
|
827
877
|
legacyRole: string | undefined,
|
|
828
878
|
roleKeyInput: string | null | undefined,
|
package/src/routes/companies.ts
CHANGED
|
@@ -114,6 +114,8 @@ export function createCompaniesRouter(ctx: AppContext) {
|
|
|
114
114
|
role: "CEO",
|
|
115
115
|
roleKey: "ceo",
|
|
116
116
|
title: "CEO",
|
|
117
|
+
capabilities:
|
|
118
|
+
"Company leadership: priorities, hiring, governance, and aligning agents to mission and budget.",
|
|
117
119
|
name: "CEO",
|
|
118
120
|
providerType,
|
|
119
121
|
heartbeatCron: "*/5 * * * *",
|
package/src/routes/issues.ts
CHANGED
|
@@ -612,10 +612,13 @@ function applyIssueMetadataToBody(
|
|
|
612
612
|
delegatedHiringIntent?: {
|
|
613
613
|
intentType: "agent_hiring_request";
|
|
614
614
|
requestedRole?: string | null;
|
|
615
|
+
requestedRoleKey?: string | null;
|
|
616
|
+
requestedTitle?: string | null;
|
|
615
617
|
requestedName?: string | null;
|
|
616
618
|
requestedManagerAgentId?: string | null;
|
|
617
619
|
requestedProviderType?: string | null;
|
|
618
620
|
requestedRuntimeModel?: string | null;
|
|
621
|
+
requestedCapabilities?: string | null;
|
|
619
622
|
};
|
|
620
623
|
}
|
|
621
624
|
| undefined
|
|
@@ -50,7 +50,16 @@ const DEFAULT_COMPANY_ID_ENV = "BOPO_DEFAULT_COMPANY_ID";
|
|
|
50
50
|
const DEFAULT_AGENT_PROVIDER_ENV = "BOPO_DEFAULT_AGENT_PROVIDER";
|
|
51
51
|
const DEFAULT_AGENT_MODEL_ENV = "BOPO_DEFAULT_AGENT_MODEL";
|
|
52
52
|
const DEFAULT_TEMPLATE_ENV = "BOPO_DEFAULT_TEMPLATE_ID";
|
|
53
|
-
type AgentProvider =
|
|
53
|
+
type AgentProvider =
|
|
54
|
+
| "codex"
|
|
55
|
+
| "claude_code"
|
|
56
|
+
| "cursor"
|
|
57
|
+
| "gemini_cli"
|
|
58
|
+
| "opencode"
|
|
59
|
+
| "openai_api"
|
|
60
|
+
| "anthropic_api"
|
|
61
|
+
| "openclaw_gateway"
|
|
62
|
+
| "shell";
|
|
54
63
|
const CEO_BOOTSTRAP_SUMMARY = "ceo bootstrap heartbeat";
|
|
55
64
|
const STARTUP_PROJECT_NAME = "Leadership Setup";
|
|
56
65
|
const CEO_STARTUP_TASK_TITLE = "Set up CEO operating files and hire founding engineer";
|
|
@@ -130,6 +139,8 @@ export async function ensureOnboardingSeed(input: {
|
|
|
130
139
|
role: "CEO",
|
|
131
140
|
roleKey: "ceo",
|
|
132
141
|
title: "CEO",
|
|
142
|
+
capabilities:
|
|
143
|
+
"Company leadership: priorities, hiring, governance, and aligning agents to mission and budget.",
|
|
133
144
|
name: "CEO",
|
|
134
145
|
providerType: agentProvider,
|
|
135
146
|
heartbeatCron: "*/5 * * * *",
|
|
@@ -399,6 +410,7 @@ function parseAgentProvider(value: unknown): AgentProvider | null {
|
|
|
399
410
|
value === "opencode" ||
|
|
400
411
|
value === "openai_api" ||
|
|
401
412
|
value === "anthropic_api" ||
|
|
413
|
+
value === "openclaw_gateway" ||
|
|
402
414
|
value === "shell"
|
|
403
415
|
) {
|
|
404
416
|
return value;
|
|
@@ -60,19 +60,6 @@ const approvalGatedActions = new Set([
|
|
|
60
60
|
const hireAgentPayloadSchema = AgentCreateRequestSchema.extend({
|
|
61
61
|
sourceIssueId: z.string().min(1).optional(),
|
|
62
62
|
sourceIssueIds: z.array(z.string().min(1)).default([]),
|
|
63
|
-
delegationIntent: z
|
|
64
|
-
.object({
|
|
65
|
-
intentType: z.literal("agent_hiring_request"),
|
|
66
|
-
requestedRole: z.string().nullable().optional(),
|
|
67
|
-
requestedName: z.string().nullable().optional(),
|
|
68
|
-
requestedManagerAgentId: z.string().nullable().optional(),
|
|
69
|
-
requestedProviderType: z
|
|
70
|
-
.enum(["claude_code", "codex", "cursor", "opencode", "gemini_cli", "openai_api", "anthropic_api", "http", "shell"])
|
|
71
|
-
.nullable()
|
|
72
|
-
.optional(),
|
|
73
|
-
requestedRuntimeModel: z.string().nullable().optional()
|
|
74
|
-
})
|
|
75
|
-
.optional(),
|
|
76
63
|
runtimeCommand: z.string().optional(),
|
|
77
64
|
runtimeArgs: z.array(z.string()).optional(),
|
|
78
65
|
runtimeCwd: z.string().optional(),
|
|
@@ -310,6 +297,7 @@ async function applyApprovalAction(db: BopoDb, companyId: string, action: string
|
|
|
310
297
|
role: resolveAgentRoleText(parsed.data.role, parsed.data.roleKey, parsed.data.title),
|
|
311
298
|
roleKey: normalizeRoleKey(parsed.data.roleKey),
|
|
312
299
|
title: normalizeTitle(parsed.data.title),
|
|
300
|
+
capabilities: normalizeCapabilities(parsed.data.capabilities),
|
|
313
301
|
name: parsed.data.name,
|
|
314
302
|
providerType: parsed.data.providerType,
|
|
315
303
|
heartbeatCron: parsed.data.heartbeatCron,
|
|
@@ -739,6 +727,11 @@ function normalizeTitle(input: string | null | undefined) {
|
|
|
739
727
|
return normalized ? normalized : null;
|
|
740
728
|
}
|
|
741
729
|
|
|
730
|
+
function normalizeCapabilities(input: string | null | undefined) {
|
|
731
|
+
const normalized = input?.trim();
|
|
732
|
+
return normalized ? normalized : null;
|
|
733
|
+
}
|
|
734
|
+
|
|
742
735
|
function resolveAgentRoleText(
|
|
743
736
|
legacyRole: string | undefined,
|
|
744
737
|
roleKeyInput: string | undefined,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
projects,
|
|
39
39
|
sql
|
|
40
40
|
} from "bopodev-db";
|
|
41
|
-
import { appendAuditEvent, appendCost } from "bopodev-db";
|
|
41
|
+
import { appendAuditEvent, appendCost, listAgents } from "bopodev-db";
|
|
42
42
|
import { parseRuntimeConfigFromAgentRow } from "../../lib/agent-config";
|
|
43
43
|
import { bootstrapRepositoryWorkspace, ensureIsolatedGitWorktree, GitRuntimeError } from "../../lib/git-runtime";
|
|
44
44
|
import {
|
|
@@ -2182,6 +2182,22 @@ async function buildHeartbeatContext(
|
|
|
2182
2182
|
const isCommentOrderWake = input.wakeContext?.reason === "issue_comment_recipient";
|
|
2183
2183
|
const promptMode = resolveHeartbeatPromptMode();
|
|
2184
2184
|
|
|
2185
|
+
const companyAgentRows = await listAgents(db, companyId);
|
|
2186
|
+
const teamRoster = companyAgentRows
|
|
2187
|
+
.filter((row) => row.status !== "terminated")
|
|
2188
|
+
.sort((a, b) => {
|
|
2189
|
+
const byName = a.name.localeCompare(b.name);
|
|
2190
|
+
return byName !== 0 ? byName : a.id.localeCompare(b.id);
|
|
2191
|
+
})
|
|
2192
|
+
.map((row) => ({
|
|
2193
|
+
id: row.id,
|
|
2194
|
+
name: row.name,
|
|
2195
|
+
role: row.role,
|
|
2196
|
+
title: row.title ?? null,
|
|
2197
|
+
capabilities: row.capabilities ?? null,
|
|
2198
|
+
status: row.status
|
|
2199
|
+
}));
|
|
2200
|
+
|
|
2185
2201
|
return {
|
|
2186
2202
|
companyId,
|
|
2187
2203
|
agentId: input.agentId,
|
|
@@ -2197,6 +2213,7 @@ async function buildHeartbeatContext(
|
|
|
2197
2213
|
role: input.agentRole,
|
|
2198
2214
|
managerAgentId: input.managerAgentId
|
|
2199
2215
|
},
|
|
2216
|
+
teamRoster,
|
|
2200
2217
|
state: input.state,
|
|
2201
2218
|
memoryContext: input.memoryContext,
|
|
2202
2219
|
runtime: input.runtime,
|
|
@@ -62,6 +62,7 @@ export async function applyTemplateManifest(
|
|
|
62
62
|
role: resolveAgentRoleText(agent.role, agent.roleKey, agent.title),
|
|
63
63
|
roleKey: normalizeRoleKey(agent.roleKey),
|
|
64
64
|
title: normalizeTitle(agent.title),
|
|
65
|
+
capabilities: normalizeCapabilities(agent.capabilities),
|
|
65
66
|
name: agent.name,
|
|
66
67
|
providerType: agent.providerType,
|
|
67
68
|
heartbeatCron: agent.heartbeatCron,
|
|
@@ -152,6 +153,11 @@ function normalizeTitle(input: string | null | undefined) {
|
|
|
152
153
|
return normalized ? normalized : null;
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
function normalizeCapabilities(input: string | null | undefined) {
|
|
157
|
+
const normalized = input?.trim();
|
|
158
|
+
return normalized ? normalized : null;
|
|
159
|
+
}
|
|
160
|
+
|
|
155
161
|
function resolveAgentRoleText(
|
|
156
162
|
legacyRole: string | undefined,
|
|
157
163
|
roleKeyInput: string | undefined,
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
createTemplate,
|
|
10
10
|
createTemplateVersion,
|
|
11
11
|
getTemplateBySlug,
|
|
12
|
-
getTemplateVersionByVersion
|
|
12
|
+
getTemplateVersionByVersion,
|
|
13
|
+
updateTemplate
|
|
13
14
|
} from "bopodev-db";
|
|
14
15
|
|
|
15
16
|
type BuiltinTemplateDefinition = {
|
|
@@ -28,7 +29,7 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
|
|
|
28
29
|
slug: "founder-startup-basic",
|
|
29
30
|
name: "Founder Startup Basic",
|
|
30
31
|
description: "Baseline operating company for solo founders launching and shipping with AI agents.",
|
|
31
|
-
version: "1.0.
|
|
32
|
+
version: "1.0.1",
|
|
32
33
|
status: "published",
|
|
33
34
|
visibility: "company",
|
|
34
35
|
variables: [
|
|
@@ -78,6 +79,10 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
|
|
|
78
79
|
{
|
|
79
80
|
key: "founder-ceo",
|
|
80
81
|
role: "CEO",
|
|
82
|
+
roleKey: "ceo",
|
|
83
|
+
title: "Founder CEO",
|
|
84
|
+
capabilities:
|
|
85
|
+
"Sets company priorities, runs leadership cadence, hires and coordinates agents toward mission outcomes.",
|
|
81
86
|
name: "Founder CEO",
|
|
82
87
|
providerType: "codex",
|
|
83
88
|
heartbeatCron: "*/15 * * * *",
|
|
@@ -119,6 +124,10 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
|
|
|
119
124
|
{
|
|
120
125
|
key: "founding-engineer",
|
|
121
126
|
role: "Founding Engineer",
|
|
127
|
+
roleKey: "engineer",
|
|
128
|
+
title: "Founding Engineer",
|
|
129
|
+
capabilities:
|
|
130
|
+
"Ships product improvements with small reviewable changes, tests, and clear handoffs to stakeholders.",
|
|
122
131
|
name: "Founding Engineer",
|
|
123
132
|
managerAgentKey: "founder-ceo",
|
|
124
133
|
providerType: "codex",
|
|
@@ -152,6 +161,10 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
|
|
|
152
161
|
{
|
|
153
162
|
key: "growth-operator",
|
|
154
163
|
role: "Growth Operator",
|
|
164
|
+
roleKey: "general",
|
|
165
|
+
title: "Growth Operator",
|
|
166
|
+
capabilities:
|
|
167
|
+
"Runs growth experiments, measures funnel impact, and feeds learnings back to leadership with clear next steps.",
|
|
155
168
|
name: "Growth Operator",
|
|
156
169
|
managerAgentKey: "founder-ceo",
|
|
157
170
|
providerType: "codex",
|
|
@@ -225,7 +238,7 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
|
|
|
225
238
|
slug: "marketing-content-engine",
|
|
226
239
|
name: "Marketing Content Engine",
|
|
227
240
|
description: "Content marketing operating template for publishing, distribution, and analytics loops.",
|
|
228
|
-
version: "1.0.
|
|
241
|
+
version: "1.0.1",
|
|
229
242
|
status: "published",
|
|
230
243
|
visibility: "company",
|
|
231
244
|
variables: [
|
|
@@ -276,6 +289,10 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
|
|
|
276
289
|
{
|
|
277
290
|
key: "head-of-marketing",
|
|
278
291
|
role: "Head of Marketing",
|
|
292
|
+
roleKey: "cmo",
|
|
293
|
+
title: "Head of Marketing",
|
|
294
|
+
capabilities:
|
|
295
|
+
"Owns marketing narrative, cross-functional alignment, and weekly performance decisions for pipeline growth.",
|
|
279
296
|
name: "Head of Marketing",
|
|
280
297
|
providerType: "codex",
|
|
281
298
|
heartbeatCron: "*/20 * * * *",
|
|
@@ -308,6 +325,10 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
|
|
|
308
325
|
{
|
|
309
326
|
key: "content-strategist",
|
|
310
327
|
role: "Content Strategist",
|
|
328
|
+
roleKey: "general",
|
|
329
|
+
title: "Content Strategist",
|
|
330
|
+
capabilities:
|
|
331
|
+
"Builds editorial calendars, briefs, and topic architecture tied to audience segments and revenue goals.",
|
|
311
332
|
name: "Content Strategist",
|
|
312
333
|
managerAgentKey: "head-of-marketing",
|
|
313
334
|
providerType: "codex",
|
|
@@ -337,6 +358,10 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
|
|
|
337
358
|
{
|
|
338
359
|
key: "content-writer",
|
|
339
360
|
role: "Content Writer",
|
|
361
|
+
roleKey: "general",
|
|
362
|
+
title: "Content Writer",
|
|
363
|
+
capabilities:
|
|
364
|
+
"Produces channel-ready drafts, headline and CTA options, and repurposing notes aligned to campaign intent.",
|
|
340
365
|
name: "Content Writer",
|
|
341
366
|
managerAgentKey: "head-of-marketing",
|
|
342
367
|
providerType: "codex",
|
|
@@ -367,6 +392,10 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
|
|
|
367
392
|
{
|
|
368
393
|
key: "distribution-manager",
|
|
369
394
|
role: "Distribution Manager",
|
|
395
|
+
roleKey: "general",
|
|
396
|
+
title: "Distribution Manager",
|
|
397
|
+
capabilities:
|
|
398
|
+
"Distributes and repurposes assets across channels with tracking discipline and weekly performance reporting.",
|
|
370
399
|
name: "Distribution Manager",
|
|
371
400
|
managerAgentKey: "head-of-marketing",
|
|
372
401
|
providerType: "codex",
|
|
@@ -482,6 +511,11 @@ export async function ensureCompanyBuiltinTemplateDefaults(db: BopoDb, companyId
|
|
|
482
511
|
version: definition.version,
|
|
483
512
|
manifestJson: JSON.stringify(manifest)
|
|
484
513
|
});
|
|
514
|
+
await updateTemplate(db, {
|
|
515
|
+
companyId,
|
|
516
|
+
id: template.id,
|
|
517
|
+
currentVersion: definition.version
|
|
518
|
+
});
|
|
485
519
|
}
|
|
486
520
|
}
|
|
487
521
|
}
|
|
@@ -16,7 +16,8 @@ export const createIssueSchema = z.object({
|
|
|
16
16
|
requestedName: z.string().nullable().optional(),
|
|
17
17
|
requestedManagerAgentId: z.string().nullable().optional(),
|
|
18
18
|
requestedProviderType: z.string().nullable().optional(),
|
|
19
|
-
requestedRuntimeModel: z.string().nullable().optional()
|
|
19
|
+
requestedRuntimeModel: z.string().nullable().optional(),
|
|
20
|
+
requestedCapabilities: z.string().max(4000).nullable().optional()
|
|
20
21
|
})
|
|
21
22
|
.optional()
|
|
22
23
|
})
|