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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bopodev-api",
3
- "version": "0.1.29",
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.29",
21
- "bopodev-agent-sdk": "0.1.29",
22
- "bopodev-db": "0.1.29"
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
@@ -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
- if (!runtimePreflightSchema.shape.providerType.safeParse(providerType).success) {
233
- return sendError(res, `Unsupported provider type: ${providerType}`, 422);
234
- }
235
- const defaultRuntimeCwd = await resolveDefaultRuntimeCwdForCompany(ctx.db, req.companyId!);
236
- let runtimeConfig: ReturnType<typeof normalizeRuntimeConfig>;
237
- try {
238
- runtimeConfig = normalizeRuntimeConfig({
239
- runtimeConfig: req.body?.runtimeConfig,
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
- const typedProviderType = providerType as
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,
@@ -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 * * * *",
@@ -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 = "codex" | "claude_code" | "cursor" | "gemini_cli" | "opencode" | "openai_api" | "anthropic_api" | "shell";
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,
@@ -10,6 +10,7 @@ export type HeartbeatProviderType =
10
10
  | "gemini_cli"
11
11
  | "openai_api"
12
12
  | "anthropic_api"
13
+ | "openclaw_gateway"
13
14
  | "http"
14
15
  | "shell";
15
16
 
@@ -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.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.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
  })