import type { DatabaseSync } from "node:sqlite"; export interface GoalKickoffPlan { assigneeAgentId: string; workspaceId: string | null; title: string; description: string; } /** * Plans the kickoff work for an active goal: the chosen owner when that agent * is still active, otherwise the top-level active agent (the CEO), bound to * the org's primary workspace. Returns null when the org has no eligible * agent — creating the first agent then kicks off every untouched goal, so a * goal never stays inert. */ export function resolveGoalKickoff(db: DatabaseSync, input: { orgId: string; goalTitle: string; goalDescription: string; ownerAgentId: string | null; }): GoalKickoffPlan | null { const assigneeAgentId = activeAgentId(db, input.orgId, input.ownerAgentId) ?? topLevelActiveAgentId(db, input.orgId); if (!assigneeAgentId) return null; const organization = db.prepare("select name from organizations where id = ?") .get(input.orgId) as { name: string }; return { assigneeAgentId, workspaceId: primaryWorkspaceId(db, input.orgId), title: goalKickoffTitle(input.goalTitle), description: goalKickoffDescription(organization.name, input.goalTitle, input.goalDescription), }; } export function goalKickoffTitle(goalTitle: string): string { return `Operationalize goal: ${goalTitle.trim()}`; } export function goalKickoffDescription(companyName: string, goalTitle: string, goalDescription: string): string { return [ `Company: ${companyName.trim()}`, `Goal: ${goalTitle.trim()}`, goalDescription.trim(), "Treat this goal as the company objective and build the organization that can deliver it: hire chief officers (hire_agent, seniority \"director\", titles like CTO/CFO/CMO/COO) for the functions the goal needs, give each officer their department objective as a task, and let them build and run their own teams. Read the bound workspace, create the first operating plan, and route decisions through approvals and messages.", ].filter((line) => line.length > 0).join("\n\n"); } /** * Waking an agent re-enables its heartbeat (same contract as the previous * runtime), so a kickoff assigned to an agent hired without a provider still * starts autonomously once a provider is configured. This also resumes the * agent's other backlog work — waking an agent wakes all of its assignments. */ export function ensureAgentHeartbeat(db: DatabaseSync, orgId: string, agentId: string): void { db.prepare(` update agents set heartbeat_enabled = 1, heartbeat_interval_sec = case when heartbeat_interval_sec > 0 then heartbeat_interval_sec else 120 end where id = ? and org_id = ? `).run(agentId, orgId); } function activeAgentId(db: DatabaseSync, orgId: string, agentId: string | null): string | null { if (!agentId) return null; const row = db.prepare("select id from agents where id = ? and org_id = ? and lifecycle = 'active'") .get(agentId, orgId) as { id: string } | undefined; return row?.id ?? null; } function topLevelActiveAgentId(db: DatabaseSync, orgId: string): string | null { const row = db.prepare(` select id from agents where org_id = ? and manager_agent_id is null and lifecycle = 'active' order by created_at asc limit 1 `).get(orgId) as { id: string } | undefined; return row?.id ?? null; } function primaryWorkspaceId(db: DatabaseSync, orgId: string): string | null { const row = db.prepare("select id from workspaces where org_id = ? and is_primary = 1 order by created_at asc limit 1") .get(orgId) as { id: string } | undefined; return row?.id ?? null; }