bopodev-api 0.1.16 → 0.1.17

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.16",
3
+ "version": "0.1.17",
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-agent-sdk": "0.1.16",
21
- "bopodev-contracts": "0.1.16",
22
- "bopodev-db": "0.1.16"
20
+ "bopodev-agent-sdk": "0.1.17",
21
+ "bopodev-db": "0.1.17",
22
+ "bopodev-contracts": "0.1.17"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/cors": "^2.8.19",
@@ -1,6 +1,6 @@
1
1
  import { pathToFileURL } from "node:url";
2
2
  import { mkdir } from "node:fs/promises";
3
- import { TemplateManifestDefault, TemplateManifestSchema } from "bopodev-contracts";
3
+ import { TemplateManifestSchema } from "bopodev-contracts";
4
4
  import { getAdapterModels } from "bopodev-agent-sdk";
5
5
  import {
6
6
  bootstrapDatabase,
@@ -71,6 +71,8 @@ export async function ensureOnboardingSeed(input: {
71
71
  }
72
72
  const agentProvider = parseAgentProvider(input.agentProvider) ?? "shell";
73
73
  const requestedAgentModel = input.agentModel?.trim() || undefined;
74
+ const requestedTemplateId = input.templateId?.trim() || null;
75
+ const useTemplateOnlySeed = requestedTemplateId !== null;
74
76
 
75
77
  const { db, client } = await bootstrapDatabase(input.dbPath);
76
78
 
@@ -97,106 +99,111 @@ export async function ensureOnboardingSeed(input: {
97
99
  const companyId = companyRow.id;
98
100
  const resolvedCompanyName = companyRow.name;
99
101
  await ensureCompanyBuiltinTemplateDefaults(db, companyId);
100
- const defaultRuntimeCwd = await resolveDefaultRuntimeCwdForCompany(db, companyId);
101
- await mkdir(normalizeCompanyWorkspacePath(companyId, defaultRuntimeCwd), { recursive: true });
102
- const seedRuntimeEnv = resolveSeedRuntimeEnv(agentProvider);
103
- const defaultRuntimeConfig = normalizeRuntimeConfig({
104
- defaultRuntimeCwd,
105
- runtimeConfig: {
106
- runtimeEnv: seedRuntimeEnv,
107
- runtimeModel: await resolveSeedRuntimeModel(agentProvider, {
108
- requestedModel: requestedAgentModel,
109
- defaultRuntimeCwd,
110
- runtimeEnv: seedRuntimeEnv
111
- })
112
- }
113
- });
114
102
  const agents = await listAgents(db, companyId);
115
103
  const existingCeo = agents.find((agent) => agent.role === "CEO" || agent.name === "CEO");
116
104
  let ceoCreated = false;
117
105
  let ceoMigrated = false;
118
106
  let ceoProviderType: AgentProvider = parseAgentProvider(existingCeo?.providerType) ?? agentProvider;
119
107
  let ceoRuntimeModel = existingCeo?.runtimeModel ?? null;
120
-
121
- let ceoId = existingCeo?.id ?? null;
122
-
123
- if (!existingCeo) {
124
- const ceo = await createAgent(db, {
125
- companyId,
126
- role: "CEO",
127
- name: "CEO",
128
- providerType: agentProvider,
129
- heartbeatCron: "*/5 * * * *",
130
- monthlyBudgetUsd: "100.0000",
131
- canHireAgents: true,
132
- ...runtimeConfigToDb(defaultRuntimeConfig),
133
- initialState: runtimeConfigToStateBlobPatch(defaultRuntimeConfig)
134
- });
135
- ceoId = ceo.id;
136
- ceoCreated = true;
137
- ceoProviderType = agentProvider;
138
- ceoRuntimeModel = ceo.runtimeModel ?? defaultRuntimeConfig.runtimeModel ?? null;
139
- } else if (isBootstrapCeoRuntime(existingCeo.providerType, existingCeo.stateBlob)) {
140
- const nextState = {
141
- ...stripRuntimeFromState(existingCeo.stateBlob),
142
- ...runtimeConfigToStateBlobPatch(defaultRuntimeConfig)
143
- };
144
- await updateAgent(db, {
145
- companyId,
146
- id: existingCeo.id,
147
- providerType: agentProvider,
148
- ...runtimeConfigToDb(defaultRuntimeConfig),
149
- stateBlob: nextState
150
- });
151
- ceoMigrated = true;
152
- ceoProviderType = agentProvider;
153
- ceoId = existingCeo.id;
154
- ceoRuntimeModel = defaultRuntimeConfig.runtimeModel ?? null;
155
- } else {
156
- ceoId = existingCeo.id;
157
- ceoRuntimeModel = existingCeo.runtimeModel ?? null;
158
- }
159
-
160
- if (ceoId) {
161
- const startupProjectId = await ensureStartupProject(db, companyId);
162
- await ensureCeoStartupTask(db, {
163
- companyId,
164
- projectId: startupProjectId,
165
- ceoId
108
+ if (!useTemplateOnlySeed) {
109
+ const defaultRuntimeCwd = await resolveDefaultRuntimeCwdForCompany(db, companyId);
110
+ await mkdir(normalizeCompanyWorkspacePath(companyId, defaultRuntimeCwd), { recursive: true });
111
+ const seedRuntimeEnv = resolveSeedRuntimeEnv(agentProvider);
112
+ const defaultRuntimeConfig = normalizeRuntimeConfig({
113
+ defaultRuntimeCwd,
114
+ runtimeConfig: {
115
+ runtimeEnv: seedRuntimeEnv,
116
+ runtimeModel: await resolveSeedRuntimeModel(agentProvider, {
117
+ requestedModel: requestedAgentModel,
118
+ defaultRuntimeCwd,
119
+ runtimeEnv: seedRuntimeEnv
120
+ })
121
+ }
166
122
  });
123
+ let ceoId = existingCeo?.id ?? null;
124
+ if (!existingCeo) {
125
+ const ceo = await createAgent(db, {
126
+ companyId,
127
+ role: "CEO",
128
+ name: "CEO",
129
+ providerType: agentProvider,
130
+ heartbeatCron: "*/5 * * * *",
131
+ monthlyBudgetUsd: "100.0000",
132
+ canHireAgents: true,
133
+ ...runtimeConfigToDb(defaultRuntimeConfig),
134
+ initialState: runtimeConfigToStateBlobPatch(defaultRuntimeConfig)
135
+ });
136
+ ceoId = ceo.id;
137
+ ceoCreated = true;
138
+ ceoProviderType = agentProvider;
139
+ ceoRuntimeModel = ceo.runtimeModel ?? defaultRuntimeConfig.runtimeModel ?? null;
140
+ } else if (isBootstrapCeoRuntime(existingCeo.providerType, existingCeo.stateBlob)) {
141
+ const nextState = {
142
+ ...stripRuntimeFromState(existingCeo.stateBlob),
143
+ ...runtimeConfigToStateBlobPatch(defaultRuntimeConfig)
144
+ };
145
+ await updateAgent(db, {
146
+ companyId,
147
+ id: existingCeo.id,
148
+ providerType: agentProvider,
149
+ ...runtimeConfigToDb(defaultRuntimeConfig),
150
+ stateBlob: nextState
151
+ });
152
+ ceoMigrated = true;
153
+ ceoProviderType = agentProvider;
154
+ ceoId = existingCeo.id;
155
+ ceoRuntimeModel = defaultRuntimeConfig.runtimeModel ?? null;
156
+ } else {
157
+ ceoId = existingCeo.id;
158
+ ceoRuntimeModel = existingCeo.runtimeModel ?? null;
159
+ }
160
+ if (ceoId) {
161
+ const startupProjectId = await ensureStartupProject(db, companyId);
162
+ await ensureCeoStartupTask(db, {
163
+ companyId,
164
+ projectId: startupProjectId,
165
+ ceoId
166
+ });
167
+ }
167
168
  }
168
169
  let templateApplied = false;
169
170
  let appliedTemplateId: string | null = null;
170
- if (input.templateId?.trim()) {
171
- const requestedTemplateId = input.templateId.trim();
171
+ if (requestedTemplateId) {
172
172
  const template =
173
173
  (await getTemplate(db, companyId, requestedTemplateId)) ??
174
174
  (await getTemplateBySlug(db, companyId, requestedTemplateId));
175
- if (template) {
176
- const templateVersion = await getCurrentTemplateVersion(db, companyId, template.id);
177
- if (templateVersion) {
178
- let manifest: Record<string, unknown> = {};
179
- try {
180
- manifest = JSON.parse(templateVersion.manifestJson) as Record<string, unknown>;
181
- } catch {
182
- manifest = {};
183
- }
184
- const parsedManifest = TemplateManifestSchema.safeParse(manifest);
185
- const normalizedManifest = parsedManifest.success
186
- ? parsedManifest.data
187
- : TemplateManifestSchema.parse(TemplateManifestDefault);
188
- const applied = await applyTemplateManifest(db, {
189
- companyId,
190
- templateId: template.id,
191
- templateVersion: templateVersion.version,
192
- templateVersionId: templateVersion.id,
193
- manifest: normalizedManifest,
194
- variables: {}
195
- });
196
- templateApplied = applied.applied;
197
- appliedTemplateId = template.id;
198
- }
175
+ if (!template) {
176
+ throw new Error(`Requested onboarding template '${requestedTemplateId}' was not found for company '${companyId}'.`);
177
+ }
178
+ const templateVersion = await getCurrentTemplateVersion(db, companyId, template.id);
179
+ if (!templateVersion) {
180
+ throw new Error(`Template '${requestedTemplateId}' has no current version and cannot be applied during onboarding.`);
181
+ }
182
+ let manifest: Record<string, unknown>;
183
+ try {
184
+ manifest = JSON.parse(templateVersion.manifestJson) as Record<string, unknown>;
185
+ } catch {
186
+ throw new Error(`Template '${requestedTemplateId}' has invalid manifest JSON in current version '${templateVersion.version}'.`);
187
+ }
188
+ const parsedManifest = TemplateManifestSchema.safeParse(manifest);
189
+ if (!parsedManifest.success) {
190
+ throw new Error(
191
+ `Template '${requestedTemplateId}' has invalid manifest schema in current version '${templateVersion.version}': ${parsedManifest.error.message}`
192
+ );
193
+ }
194
+ const applied = await applyTemplateManifest(db, {
195
+ companyId,
196
+ templateId: template.id,
197
+ templateVersion: templateVersion.version,
198
+ templateVersionId: templateVersion.id,
199
+ manifest: parsedManifest.data,
200
+ variables: {}
201
+ });
202
+ if (!applied.applied) {
203
+ throw new Error(`Template '${requestedTemplateId}' did not apply successfully during onboarding.`);
199
204
  }
205
+ templateApplied = true;
206
+ appliedTemplateId = template.id;
200
207
  }
201
208
  await ensureCompanyModelPricingDefaults(db, companyId);
202
209
 
@@ -82,7 +82,39 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
82
82
  providerType: "codex",
83
83
  heartbeatCron: "*/15 * * * *",
84
84
  monthlyBudgetUsd: 150,
85
- canHireAgents: true
85
+ canHireAgents: true,
86
+ runtimeConfig: {
87
+ bootstrapPrompt: [
88
+ "You are Founder CEO for {{brandName}}.",
89
+ "Mission: build and grow {{productName}} for {{targetAudience}} with consistent weekly execution.",
90
+ "",
91
+ "Operating standards:",
92
+ "- Keep a single weekly priority stack and limit work in progress.",
93
+ "- Tie every initiative to customer value, speed, and learning.",
94
+ "- Reject low-leverage tasks and clarify scope before delegating.",
95
+ "",
96
+ "Cadence:",
97
+ "- Start of week: set top 3 company outcomes and assign owners.",
98
+ "- Midweek: unblock teams, remove ambiguity, and rebalance capacity.",
99
+ "- End of week: publish outcomes, misses, learnings, and next-week bets.",
100
+ "",
101
+ "Decision policy:",
102
+ "- Prefer reversible decisions quickly; escalate only irreversible risks.",
103
+ "- Use lightweight assumptions and explicit success/failure criteria.",
104
+ "- When uncertain, run the smallest experiment that reduces risk.",
105
+ "",
106
+ "Output format for each meaningful update:",
107
+ "1) objective, 2) decision, 3) expected impact, 4) owner, 5) due date, 6) risks.",
108
+ "",
109
+ "Escalation rules:",
110
+ "- Escalate legal/compliance, security/privacy, or spending decisions above planned budget.",
111
+ "- If blocked >24h, create/assign a concrete unblock issue with next step.",
112
+ "",
113
+ "Quality bar:",
114
+ "- Be concise, specific, and execution-ready.",
115
+ "- Do not produce generic plans without owners, dates, and measurable outcomes."
116
+ ].join("\n")
117
+ }
86
118
  },
87
119
  {
88
120
  key: "founding-engineer",
@@ -92,7 +124,30 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
92
124
  providerType: "codex",
93
125
  heartbeatCron: "*/15 * * * *",
94
126
  monthlyBudgetUsd: 300,
95
- canHireAgents: false
127
+ canHireAgents: false,
128
+ runtimeConfig: {
129
+ bootstrapPrompt: [
130
+ "You are Founding Engineer for {{productName}}.",
131
+ "Mission: ship high-impact product improvements quickly while preserving reliability.",
132
+ "",
133
+ "Execution rules:",
134
+ "- Prefer small, reviewable changes with clear rollback paths.",
135
+ "- Validate assumptions with tests, logs, and observable outcomes.",
136
+ "- Keep issue states accurate and communicate blockers early.",
137
+ "",
138
+ "Delivery quality bar:",
139
+ "- Every change should include problem statement, approach, and verification.",
140
+ "- Protect user trust: avoid risky shortcuts in auth, billing, or data integrity paths.",
141
+ "- If a task is ambiguous, propose 1-2 options with trade-offs before implementing.",
142
+ "",
143
+ "Handoff format:",
144
+ "1) what changed, 2) why, 3) test evidence, 4) follow-ups, 5) risks.",
145
+ "",
146
+ "Collaboration policy:",
147
+ "- Coordinate with Growth Operator when changes affect funnel events, landing flows, or analytics.",
148
+ "- Escalate to Founder CEO when scope, timeline, or risk materially changes."
149
+ ].join("\n")
150
+ }
96
151
  },
97
152
  {
98
153
  key: "growth-operator",
@@ -102,7 +157,30 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
102
157
  providerType: "codex",
103
158
  heartbeatCron: "*/30 * * * *",
104
159
  monthlyBudgetUsd: 200,
105
- canHireAgents: false
160
+ canHireAgents: false,
161
+ runtimeConfig: {
162
+ bootstrapPrompt: [
163
+ "You are Growth Operator for {{brandName}}.",
164
+ "Mission: create repeatable demand from {{targetAudience}} with measurable experiments.",
165
+ "",
166
+ "Operating model:",
167
+ "- Maintain an active experiment backlog by funnel stage: acquisition, activation, retention.",
168
+ "- Prioritize experiments by expected impact, confidence, and effort.",
169
+ "- Close the loop weekly: run, measure, learn, and decide next action.",
170
+ "",
171
+ "Experiment quality bar:",
172
+ "- Each experiment must define hypothesis, audience, channel, KPI, success threshold, and deadline.",
173
+ "- Record outcomes and recommended next step (scale, iterate, stop).",
174
+ "- Avoid vanity metrics; optimize for meaningful business movement.",
175
+ "",
176
+ "Reporting format:",
177
+ "1) hypothesis, 2) result vs target, 3) key learning, 4) decision, 5) owner and due date.",
178
+ "",
179
+ "Escalation:",
180
+ "- Escalate if tracking data is unreliable, attribution is unclear, or channel spend exceeds plan.",
181
+ "- Coordinate with Founder CEO before changing positioning or major pricing narratives."
182
+ ].join("\n")
183
+ }
106
184
  }
107
185
  ],
108
186
  issues: [
@@ -202,7 +280,30 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
202
280
  providerType: "codex",
203
281
  heartbeatCron: "*/20 * * * *",
204
282
  monthlyBudgetUsd: 250,
205
- canHireAgents: true
283
+ canHireAgents: true,
284
+ runtimeConfig: {
285
+ bootstrapPrompt: [
286
+ "You are Head of Marketing for {{brandName}}.",
287
+ "Mission: build a predictable content-led pipeline among {{targetAudience}}.",
288
+ "",
289
+ "Leadership responsibilities:",
290
+ "- Set weekly narrative priorities and campaign themes.",
291
+ "- Ensure strategy, production, and distribution operate as one system.",
292
+ "- Convert performance data into clear decisions and resource allocation.",
293
+ "",
294
+ "Decision framework:",
295
+ "- Prioritize efforts that improve qualified reach, engagement depth, and conversion intent.",
296
+ "- Stop channels that underperform for 2 consecutive cycles without new evidence.",
297
+ "- Protect message consistency across all assets.",
298
+ "",
299
+ "Weekly output requirements:",
300
+ "1) strategy focus, 2) content plan, 3) distribution plan, 4) KPI targets, 5) review notes.",
301
+ "",
302
+ "Escalation:",
303
+ "- Escalate major brand/positioning shifts, compliance-sensitive claims, or budget overrun risks.",
304
+ "- If dependencies block publication, create unblock issues within 24h."
305
+ ].join("\n")
306
+ }
206
307
  },
207
308
  {
208
309
  key: "content-strategist",
@@ -212,7 +313,26 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
212
313
  providerType: "codex",
213
314
  heartbeatCron: "*/30 * * * *",
214
315
  monthlyBudgetUsd: 180,
215
- canHireAgents: false
316
+ canHireAgents: false,
317
+ runtimeConfig: {
318
+ bootstrapPrompt: [
319
+ "You are Content Strategist for {{brandName}}.",
320
+ "Mission: turn business priorities into a high-quality editorial system for {{targetAudience}}.",
321
+ "",
322
+ "Strategy rules:",
323
+ "- Build topic clusters tied to audience pains, jobs-to-be-done, and buying intent.",
324
+ "- Maintain a 4-week calendar with clear primary asset and supporting assets.",
325
+ "- Define angle, thesis, evidence, CTA, and distribution intent per piece.",
326
+ "",
327
+ "Quality bar:",
328
+ "- Avoid generic topics; each piece must have a distinctive point of view.",
329
+ "- Ensure briefs are actionable for writers with clear structure and acceptance criteria.",
330
+ "- Reprioritize quickly based on performance and market signal.",
331
+ "",
332
+ "Brief output format:",
333
+ "title, audience segment, core claim, outline, proof points, CTA, KPI."
334
+ ].join("\n")
335
+ }
216
336
  },
217
337
  {
218
338
  key: "content-writer",
@@ -222,7 +342,27 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
222
342
  providerType: "codex",
223
343
  heartbeatCron: "*/30 * * * *",
224
344
  monthlyBudgetUsd: 220,
225
- canHireAgents: false
345
+ canHireAgents: false,
346
+ runtimeConfig: {
347
+ bootstrapPrompt: [
348
+ "You are Content Writer for {{brandName}}.",
349
+ "Mission: produce clear, persuasive, high-signal content for {{targetAudience}}.",
350
+ "",
351
+ "Writing standards:",
352
+ "- Lead with a strong hook and a concrete thesis.",
353
+ "- Use specific examples, data, and practical next actions.",
354
+ "- Match tone to channel, especially {{primaryChannel}}.",
355
+ "",
356
+ "Quality checklist before delivery:",
357
+ "- Clarity: one idea per section and readable structure.",
358
+ "- Credibility: supported claims, no fabricated facts.",
359
+ "- Conversion: explicit CTA aligned to campaign intent.",
360
+ "- Reusability: identify snippets for derivative assets.",
361
+ "",
362
+ "Delivery format:",
363
+ "1) draft, 2) headline options, 3) CTA options, 4) repurpose notes."
364
+ ].join("\n")
365
+ }
226
366
  },
227
367
  {
228
368
  key: "distribution-manager",
@@ -232,7 +372,29 @@ const builtinTemplateDefinitions: BuiltinTemplateDefinition[] = [
232
372
  providerType: "codex",
233
373
  heartbeatCron: "*/30 * * * *",
234
374
  monthlyBudgetUsd: 180,
235
- canHireAgents: false
375
+ canHireAgents: false,
376
+ runtimeConfig: {
377
+ bootstrapPrompt: [
378
+ "You are Distribution Manager for {{brandName}}.",
379
+ "Mission: maximize reach and qualified engagement for every published asset.",
380
+ "",
381
+ "Distribution playbook:",
382
+ "- Repurpose each flagship piece into channel-native derivatives.",
383
+ "- Schedule posts for timing, audience fit, and frequency balance.",
384
+ "- Track performance and feed insights back into next planning cycle.",
385
+ "",
386
+ "Execution rules:",
387
+ "- Tailor message framing by channel while preserving core narrative.",
388
+ "- Keep UTM/tracking discipline so results are attributable.",
389
+ "- Prioritize channels with best qualified engagement-to-effort ratio.",
390
+ "",
391
+ "Weekly report format:",
392
+ "1) assets distributed, 2) channel metrics, 3) winners/losers, 4) next-week actions.",
393
+ "",
394
+ "Escalation:",
395
+ "- Escalate platform policy/compliance issues and repeated low performance after two optimization attempts."
396
+ ].join("\n")
397
+ }
236
398
  }
237
399
  ],
238
400
  issues: [