From 54bcc77ef76e75c153048844dc4da52cfd123ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 16 Mar 2026 13:07:14 -0400 Subject: [PATCH] update --- package.json | 2 +- src/main/api-server.ts | 428 ++++ src/main/browser-manager.ts | 485 +++++ src/main/database.ts | 996 +++++++++- src/main/index.ts | 548 +++++- src/main/prompt-builder.ts | 720 ++++++- src/main/templates.ts | 1742 +++++++++++++++++ src/preload/index.ts | 39 + src/renderer/App.tsx | 335 +++- src/renderer/components/CompanyOnboarding.tsx | 320 ++- src/renderer/components/automation.tsx | 1731 ++++++++++++++++ src/renderer/components/communication.tsx | 1438 ++++++++++++++ src/renderer/components/dashboard.tsx | 302 ++- src/renderer/components/hiring.tsx | 339 ++++ src/renderer/components/inbox.tsx | 537 +++++ src/renderer/components/social.tsx | 501 +++++ src/shared/contracts.ts | 256 ++- src/shared/types.ts | 486 ++++- tests/integration/autonomous-chain.spec.ts | 279 +++ 19 files changed, 11420 insertions(+), 64 deletions(-) create mode 100644 src/main/browser-manager.ts create mode 100644 src/main/templates.ts create mode 100644 src/renderer/components/automation.tsx create mode 100644 src/renderer/components/communication.tsx create mode 100644 src/renderer/components/hiring.tsx create mode 100644 src/renderer/components/inbox.tsx create mode 100644 src/renderer/components/social.tsx diff --git a/package.json b/package.json index e5b19494..2ddcfbb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agent-company", - "version": "0.1.0", + "version": "0.3.0", "private": true, "type": "module", "description": "Cross-platform Electron desktop control plane for local-first AI agent companies.", diff --git a/src/main/api-server.ts b/src/main/api-server.ts index 9ea3cc71..cc7185ba 100644 --- a/src/main/api-server.ts +++ b/src/main/api-server.ts @@ -49,6 +49,7 @@ export class AgentApiServer { private db: AppDatabase; private onDomainChanged: () => void; private onWakeAgent: (agentId: string, companyId: string, trigger: string) => void; + private onBrowserAction: ((actionId: string, socialAccountId: string, actionType: string, payload: Record) => void) | null = null; constructor(db: AppDatabase, onDomainChanged: () => void, onWakeAgent: (agentId: string, companyId: string, trigger: string) => void) { this.db = db; @@ -57,6 +58,10 @@ export class AgentApiServer { this.registerRoutes(); } + setBrowserActionHandler(handler: (actionId: string, socialAccountId: string, actionType: string, payload: Record) => void) { + this.onBrowserAction = handler; + } + getPort(): number { return this.port; } @@ -960,7 +965,430 @@ export class AgentApiServer { tasksDone: rTasks.filter((t) => t.status === "done").length, }; }), + socialAccounts: snapshot.socialAccounts + .filter((a) => a.companyId === token.companyId) + .map((a) => ({ + id: a.id, + platform: a.platform, + accountName: a.accountName, + displayName: a.displayName, + status: a.status, + requireApproval: a.requireApproval, + })), }); }); + + // ── Browser Automation API (for agents) ──────────────────────────── + + // POST /api/companies/:companyId/browser/action — Submit a browser action + this.addRoute("POST", "/api/companies/:companyId/browser/action", async (_req, res, params, token, body) => { + const companyId = params.companyId!; + if (companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + + const actionSchema = z.object({ + socialAccountId: z.string().uuid(), + actionType: z.enum(["post", "reply", "like", "follow", "browse_feed", "screenshot", "navigate", "search", "dm", "custom_script"]), + payload: z.record(z.string(), z.unknown()).default({}), + }); + + const parsed = actionSchema.safeParse(body); + if (!parsed.success) { + this.json(res, 400, { error: "Invalid browser action payload", details: parsed.error.issues }); + return; + } + + const input = parsed.data; + + // Verify the social account belongs to this company + const snapshot = this.db.listSnapshot(); + const account = snapshot.socialAccounts.find(a => a.id === input.socialAccountId && a.companyId === companyId); + if (!account) { + this.json(res, 404, { error: "Social account not found" }); + return; + } + + // Check if approval is required for posting actions + const postActions = ["post", "reply", "dm"]; + if (account.requireApproval && postActions.includes(input.actionType)) { + // Create an approval request + const approvalId = this.db.requestApproval({ + companyId, + type: "dangerous_command", + relatedTaskId: null, + requestedByAgentId: token.agentId, + payloadSummary: `${input.actionType} on ${account.platform}/@${account.accountName}: ${String(input.payload.content || "").slice(0, 100)}`, + impactSummary: `Will ${input.actionType} on ${account.displayName || account.accountName}'s ${account.platform} account. Content will be publicly visible.`, + }); + + // Save the action in approval_required state + const actionId = this.db.saveBrowserAction({ + companyId, + socialAccountId: input.socialAccountId, + agentId: token.agentId, + actionType: input.actionType, + payloadJson: JSON.stringify(input.payload), + status: "approval_required", + approvalId, + }); + + this.db.addActivity({ + companyId, + actor: token.agentId, + action: "browser.action_pending_approval", + entityType: "browser_action", + entityId: actionId, + detail: `${input.actionType} on ${account.platform} requires approval`, + }); + + this.onDomainChanged(); + this.json(res, 202, { id: actionId, status: "approval_required", approvalId, message: "Action requires Board approval before execution" }); + return; + } + + // Save the action + const actionId = this.db.saveBrowserAction({ + companyId, + socialAccountId: input.socialAccountId, + agentId: token.agentId, + actionType: input.actionType, + payloadJson: JSON.stringify(input.payload), + status: "queued", + }); + + this.db.addActivity({ + companyId, + actor: token.agentId, + action: "browser.action_queued", + entityType: "browser_action", + entityId: actionId, + detail: `${input.actionType} on ${account.platform}/@${account.accountName}`, + }); + + this.onDomainChanged(); + + // Execute the action asynchronously via the browser manager callback + if (this.onBrowserAction) { + this.onBrowserAction(actionId, input.socialAccountId, input.actionType, input.payload as Record); + } + + this.json(res, 201, { id: actionId, status: "queued", message: "Browser action queued for execution" }); + }); + + // GET /api/companies/:companyId/social-accounts — List social accounts + this.addRoute("GET", "/api/companies/:companyId/social-accounts", (_req, res, params, token) => { + if (params.companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + const accounts = this.db.listSocialAccounts(params.companyId!); + // Don't expose credential secret IDs to agents + const sanitized = accounts.map(a => ({ + id: a.id, + platform: a.platform, + accountName: a.accountName, + displayName: a.displayName, + profileUrl: a.profileUrl, + status: a.status, + requireApproval: a.requireApproval, + })); + this.json(res, 200, sanitized); + }); + + // GET /api/companies/:companyId/browser/actions — List browser action history + this.addRoute("GET", "/api/companies/:companyId/browser/actions", (req, res, params, token) => { + if (params.companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + const url = new URL(req.url ?? "/", `http://127.0.0.1:${this.port}`); + const socialAccountId = url.searchParams.get("socialAccountId") ?? undefined; + const status = url.searchParams.get("status") ?? undefined; + const limit = Number(url.searchParams.get("limit") || 50); + + const actions = this.db.listBrowserActions(params.companyId!, { socialAccountId, status, limit }); + this.json(res, 200, actions); + }); + + // ── Documents API ───────────────────────────────────────────────── + + // POST /api/companies/:companyId/documents — Create a document + this.addRoute("POST", "/api/companies/:companyId/documents", (_req, res, params, token, body) => { + if (params.companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + const data = body as Record; + const id = this.db.saveDocument({ + companyId: token.companyId, + type: String(data.type ?? "knowledge_article"), + title: String(data.title ?? "Untitled"), + content: String(data.content ?? ""), + authorAgentId: token.agentId, + reviewerAgentId: data.reviewerAgentId ? String(data.reviewerAgentId) : null, + projectId: data.projectId ? String(data.projectId) : null, + goalId: data.goalId ? String(data.goalId) : null, + status: String(data.status ?? "draft"), + tagsJson: data.tagsJson ? String(data.tagsJson) : "[]", + }); + this.onDomainChanged(); + this.json(res, 201, { id }); + }); + + // GET /api/companies/:companyId/documents — List documents + this.addRoute("GET", "/api/companies/:companyId/documents", (req, res, params, token) => { + if (params.companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + const snapshot = this.db.listSnapshot(); + const docs = snapshot.documents.filter(d => d.companyId === token.companyId); + this.json(res, 200, docs); + }); + + // ── Meetings API ────────────────────────────────────────────────── + + // POST /api/companies/:companyId/meetings — Schedule a meeting + this.addRoute("POST", "/api/companies/:companyId/meetings", (_req, res, params, token, body) => { + if (params.companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + const data = body as Record; + const id = this.db.saveMeeting({ + companyId: token.companyId, + type: String(data.type ?? "department_sync"), + title: String(data.title ?? "Meeting"), + organizerAgentId: token.agentId, + participantAgentIds: data.participantAgentIds ? String(data.participantAgentIds) : "[]", + scheduledAt: String(data.scheduledAt ?? new Date().toISOString()), + durationMinutes: Number(data.durationMinutes ?? 30), + agendaJson: data.agendaJson ? String(data.agendaJson) : "[]", + status: "scheduled", + }); + this.onDomainChanged(); + this.json(res, 201, { id }); + }); + + // GET /api/companies/:companyId/meetings — List meetings + this.addRoute("GET", "/api/companies/:companyId/meetings", (_req, res, params, token) => { + if (params.companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + const snapshot = this.db.listSnapshot(); + const meetings = snapshot.meetings.filter(m => m.companyId === token.companyId); + this.json(res, 200, meetings); + }); + + // PATCH /api/meetings/:meetingId — Update meeting (add notes, decisions, etc.) + this.addRoute("PATCH", "/api/meetings/:meetingId", (_req, res, params, token, body) => { + const data = body as Record; + this.db.saveMeeting({ + id: params.meetingId, + companyId: token.companyId, + type: data.type ? String(data.type) : "department_sync", + title: data.title ? String(data.title) : "Meeting", + notesJson: data.notesJson ? String(data.notesJson) : undefined, + decisionsJson: data.decisionsJson ? String(data.decisionsJson) : undefined, + actionItemsJson: data.actionItemsJson ? String(data.actionItemsJson) : undefined, + status: data.status ? String(data.status) : undefined, + scheduledAt: data.scheduledAt ? String(data.scheduledAt) : new Date().toISOString(), + }); + this.onDomainChanged(); + this.json(res, 200, { updated: true }); + }); + + // ── Knowledge Base API ──────────────────────────────────────────── + + // POST /api/companies/:companyId/knowledge — Add knowledge entry + this.addRoute("POST", "/api/companies/:companyId/knowledge", (_req, res, params, token, body) => { + if (params.companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + const data = body as Record; + const id = this.db.saveKnowledgeEntry({ + companyId: token.companyId, + category: String(data.category ?? "lesson_learned"), + topic: String(data.topic ?? ""), + content: String(data.content ?? ""), + authorAgentId: token.agentId, + importance: String(data.importance ?? "medium"), + tagsJson: data.tagsJson ? String(data.tagsJson) : "[]", + referencedEntityType: data.referencedEntityType ? String(data.referencedEntityType) : null, + referencedEntityId: data.referencedEntityId ? String(data.referencedEntityId) : null, + }); + this.onDomainChanged(); + this.json(res, 201, { id }); + }); + + // GET /api/companies/:companyId/knowledge — List knowledge entries + this.addRoute("GET", "/api/companies/:companyId/knowledge", (_req, res, params, token) => { + if (params.companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + const snapshot = this.db.listSnapshot(); + const entries = snapshot.knowledgeBase.filter(k => k.companyId === token.companyId); + this.json(res, 200, entries); + }); + + // ── Agent Messaging API ────────────────────────────────────────────── + + // POST /api/companies/:companyId/messages — Send a message to another agent + this.addRoute("POST", "/api/companies/:companyId/messages", (_req, res, params, token, body) => { + const companyId = params.companyId!; + if (companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + + const messageSchema = z.object({ + toAgentId: z.string().uuid().nullable().optional(), + channel: z.enum(["direct", "department", "company", "project", "incident"]).default("direct"), + channelTargetId: z.string().nullable().optional(), + subject: z.string().min(1).max(200), + body: z.string().min(1).max(8000), + priority: z.enum(["urgent", "normal", "low"]).default("normal"), + parentMessageId: z.string().uuid().nullable().optional(), + attachmentsJson: z.string().default("[]"), + }); + + const parsed = messageSchema.safeParse(body); + if (!parsed.success) { + this.json(res, 400, { error: "Invalid input", details: parsed.error.flatten() }); + return; + } + + const msg = parsed.data; + const messageId = this.db.sendAgentMessage({ + companyId, + fromAgentId: token.agentId, + toAgentId: msg.toAgentId, + channel: msg.channel, + channelTargetId: msg.channelTargetId, + subject: msg.subject, + body: msg.body, + priority: msg.priority, + parentMessageId: msg.parentMessageId, + attachmentsJson: msg.attachmentsJson, + }); + + // Wake the recipient if it's a direct message + if (msg.toAgentId && msg.channel === "direct") { + this.onWakeAgent(msg.toAgentId, companyId, "message"); + } + + // Wake all agents in the department if it's a department broadcast + if (msg.channel === "department" && msg.channelTargetId) { + const snapshot = this.db.listSnapshot(); + const deptAgents = snapshot.agents.filter( + (a) => a.department === msg.channelTargetId && a.companyId === companyId && a.id !== token.agentId && a.status !== "terminated", + ); + for (const a of deptAgents) { + this.onWakeAgent(a.id, companyId, "message"); + } + } + + this.onDomainChanged(); + this.json(res, 201, { id: messageId }); + }); + + // GET /api/companies/:companyId/messages — List messages for the calling agent + this.addRoute("GET", "/api/companies/:companyId/messages", (req, res, params, token) => { + const companyId = params.companyId!; + if (companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + + const url = new URL(req.url ?? "/", `http://127.0.0.1:${this.port}`); + const channel = url.searchParams.get("channel") ?? undefined; + const unreadOnly = url.searchParams.get("unreadOnly") === "true"; + const limitStr = url.searchParams.get("limit"); + const limit = limitStr ? Math.min(Math.max(parseInt(limitStr, 10) || 50, 1), 200) : 50; + + const messages = this.db.listAgentMessages(companyId, { + agentId: token.agentId, + channel, + unreadOnly, + limit, + }); + this.json(res, 200, messages); + }); + + // POST /api/companies/:companyId/messages/:messageId/read — Mark message as read + this.addRoute("POST", "/api/companies/:companyId/messages/:messageId/read", (_req, res, params, token) => { + const companyId = params.companyId!; + if (companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + this.db.markMessageRead(params.messageId!, companyId); + this.json(res, 200, { ok: true }); + }); + + // ── Sprint API ─────────────────────────────────────────────────────── + + // GET /api/companies/:companyId/sprints — List sprints + this.addRoute("GET", "/api/companies/:companyId/sprints", (_req, res, params, token) => { + const companyId = params.companyId!; + if (companyId !== token.companyId) { + this.json(res, 403, { error: "Company mismatch" }); + return; + } + const snapshot = this.db.listSnapshot(); + const sprints = snapshot.sprints.filter(s => s.companyId === companyId); + this.json(res, 200, sprints); + }); + + // ── Performance Metrics API ────────────────────────────────────────── + + // GET /api/agents/me/metrics — Get the calling agent's performance metrics + this.addRoute("GET", "/api/agents/me/metrics", (_req, res, _params, token) => { + const companyMetrics = this.db.getCompanyMetrics(token.companyId, 30); + const agentMetric = companyMetrics.agentMetrics.find(m => m.agentId === token.agentId); + if (!agentMetric) { + this.json(res, 404, { error: "Metrics not found" }); + return; + } + this.json(res, 200, agentMetric); + }); + + // ── Org Chart / Agent Details API ──────────────────────────────────── + + // GET /api/agents/:agentId — Get a specific agent's details + this.addRoute("GET", "/api/agents/:agentId", (_req, res, params, token) => { + const snapshot = this.db.listSnapshot(); + const agent = snapshot.agents.find(a => a.id === params.agentId && a.companyId === token.companyId); + if (!agent) { + this.json(res, 404, { error: "Agent not found" }); + return; + } + const directReports = snapshot.agents.filter(a => a.reportsTo === agent.id && a.companyId === token.companyId); + const manager = agent.reportsTo ? snapshot.agents.find(a => a.id === agent.reportsTo) : null; + this.json(res, 200, { + ...agent, + manager: manager ? { id: manager.id, name: manager.name, role: manager.role } : null, + directReports: directReports.map(r => ({ id: r.id, name: r.name, role: r.role, status: r.status })), + }); + }); + + // ── Wake / Heartbeat API ───────────────────────────────────────────── + + // POST /api/agents/:agentId/wake — Request to wake another agent + this.addRoute("POST", "/api/agents/:agentId/wake", (_req, res, params, token, body) => { + const targetAgentId = params.agentId!; + const wakeSchema = z.object({ + reason: z.string().min(1).max(200).default("peer_request"), + }); + const parsed = wakeSchema.safeParse(body); + const reason = parsed.success ? parsed.data.reason : "peer_request"; + this.onWakeAgent(targetAgentId, token.companyId, reason); + this.json(res, 200, { ok: true, message: `Wake requested for agent ${targetAgentId}` }); + }); } } diff --git a/src/main/browser-manager.ts b/src/main/browser-manager.ts new file mode 100644 index 00000000..6eda8628 --- /dev/null +++ b/src/main/browser-manager.ts @@ -0,0 +1,485 @@ +import { BrowserWindow, session, type NativeImage } from "electron"; +import { existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import log from "electron-log/main.js"; + +/** + * Browser Automation Manager — Electron-native edition. + * + * Uses Electron's own BrowserWindow and persistent sessions instead of + * external Playwright, so it works in packaged apps with zero extra deps. + * + * Each social account gets its own Electron session partition so cookies, + * localStorage, and login state are isolated and persisted across app restarts. + */ + +export interface BrowserAction { + id: string; + socialAccountId: string; + actionType: string; + payload: Record; +} + +export interface BrowserActionResult { + success: boolean; + summary: string; + screenshotPath: string | null; + error: string | null; +} + +interface QueuedAction { + action: BrowserAction; + resolve: (result: BrowserActionResult) => void; + reject: (error: Error) => void; +} + +export class BrowserManager { + private screenshotDir: string; + private queues = new Map(); + private processing = new Set(); + /** Track open login windows so we don't open duplicates */ + private loginWindows = new Map(); + + constructor(profileDir: string) { + this.screenshotDir = join(profileDir, "browser-screenshots"); + if (!existsSync(this.screenshotDir)) mkdirSync(this.screenshotDir, { recursive: true }); + } + + /** + * Get a persistent Electron session for a social account. + * Using `persist:` prefix ensures data survives app restarts. + */ + private getSession(socialAccountId: string): Electron.Session { + return session.fromPartition(`persist:social-${socialAccountId}`); + } + + /** + * Launch a real browser window for manual login. + * The window uses a persistent session so cookies are saved. + * Returns a promise that resolves when the user closes the window. + */ + async launchLoginBrowser( + socialAccountId: string, + platform: string, + loginUrl?: string, + ): Promise { + // Don't open duplicate login windows for the same account + const existing = this.loginWindows.get(socialAccountId); + if (existing && !existing.isDestroyed()) { + existing.focus(); + return { success: true, summary: "Login window already open", screenshotPath: null, error: null }; + } + + const defaultUrls: Record = { + twitter: "https://x.com/i/flow/login", + linkedin: "https://www.linkedin.com/login", + instagram: "https://www.instagram.com/accounts/login/", + facebook: "https://www.facebook.com/login", + reddit: "https://www.reddit.com/login", + youtube: "https://accounts.google.com/signin", + tiktok: "https://www.tiktok.com/login", + bluesky: "https://bsky.app", + other: "https://google.com", + }; + + const url = loginUrl || defaultUrls[platform] || defaultUrls.other; + const ses = this.getSession(socialAccountId); + + log.info(`[browser] Opening login window for ${platform} → ${url}`); + + const win = new BrowserWindow({ + width: 1280, + height: 860, + title: `Login to ${platform} — AgentCompany`, + webPreferences: { + session: ses, + // Standard web page — no node integration, no preload + nodeIntegration: false, + contextIsolation: true, + sandbox: true, + }, + }); + + this.loginWindows.set(socialAccountId, win); + + // Remove menu bar for a cleaner look + win.setMenuBarVisibility(false); + + // Load the login page + try { + await win.loadURL(url); + } catch (err) { + log.error(`[browser] Failed to load ${url}: ${err}`); + // Don't fail — the page might redirect or have an expected error + } + + // Wait for the user to close the window + return new Promise((resolve) => { + win.on("closed", () => { + this.loginWindows.delete(socialAccountId); + log.info(`[browser] Login window closed for ${platform} — session saved`); + resolve({ + success: true, + summary: `Login session saved for ${platform}`, + screenshotPath: null, + error: null, + }); + }); + }); + } + + /** + * Check if browser automation is available. + * Always true — we use Electron's own Chromium. + */ + async checkPlaywright(): Promise { + return true; + } + + /** + * Queue a browser action for execution. + */ + async executeAction(action: BrowserAction): Promise { + return new Promise((resolve, reject) => { + const queue = this.queues.get(action.socialAccountId) ?? []; + queue.push({ action, resolve, reject }); + this.queues.set(action.socialAccountId, queue); + void this.processQueue(action.socialAccountId); + }); + } + + private async processQueue(socialAccountId: string) { + if (this.processing.has(socialAccountId)) return; + const queue = this.queues.get(socialAccountId); + if (!queue || queue.length === 0) return; + + this.processing.add(socialAccountId); + while (queue.length > 0) { + const item = queue.shift()!; + try { + const result = await this.runAction(item.action); + item.resolve(result); + } catch (error) { + item.reject(error instanceof Error ? error : new Error(String(error))); + } + } + this.processing.delete(socialAccountId); + } + + /** + * Execute a single browser action using an Electron BrowserWindow. + */ + private async runAction(action: BrowserAction): Promise { + const ses = this.getSession(action.socialAccountId); + const payload = action.payload; + const screenshotPath = join(this.screenshotDir, `${action.id}.png`); + + log.info(`[browser] Executing ${action.actionType} for account ${action.socialAccountId}`); + + // Create a visible browser window for the action + const win = new BrowserWindow({ + width: 1280, + height: 860, + title: `AgentCompany — ${action.actionType}`, + show: true, + webPreferences: { + session: ses, + nodeIntegration: false, + contextIsolation: true, + sandbox: true, + }, + }); + win.setMenuBarVisibility(false); + + try { + let summary = "Action completed"; + + switch (action.actionType) { + case "navigate": { + const url = String(payload.url || "https://google.com"); + await win.loadURL(url); + await this.waitForLoad(win); + const title = await win.webContents.executeJavaScript("document.title"); + summary = `Navigated to ${title}`; + break; + } + + case "post": { + const platform = String(payload.platform || "twitter"); + const content = String(payload.content || ""); + const postUrl = this.getComposeUrl(platform); + await win.loadURL(postUrl); + await this.waitForLoad(win); + await this.delay(2000); + + if (platform === "twitter") { + // Type into tweet composer + await win.webContents.executeJavaScript(` + (async () => { + const editor = document.querySelector('[data-testid="tweetTextarea_0"], [role="textbox"]'); + if (editor) { + editor.focus(); + document.execCommand('insertText', false, ${JSON.stringify(content)}); + } + })() + `); + await this.delay(1000); + // Click post button + await win.webContents.executeJavaScript(` + (() => { + const btn = document.querySelector('[data-testid="tweetButton"], [data-testid="tweetButtonInline"]'); + if (btn) btn.click(); + })() + `); + await this.delay(3000); + } else if (platform === "linkedin") { + await win.webContents.executeJavaScript(` + (() => { + const btn = document.querySelector('button.share-box-feed-entry__trigger'); + if (btn) btn.click(); + })() + `); + await this.delay(2000); + await win.webContents.executeJavaScript(` + (async () => { + const editor = document.querySelector('[role="textbox"][contenteditable="true"]'); + if (editor) { + editor.focus(); + document.execCommand('insertText', false, ${JSON.stringify(content)}); + } + })() + `); + await this.delay(1000); + await win.webContents.executeJavaScript(` + (() => { + const btn = document.querySelector('button.share-actions__primary-action'); + if (btn) btn.click(); + })() + `); + await this.delay(3000); + } + summary = `Posted to ${platform}: "${content.slice(0, 50)}..."`; + break; + } + + case "reply": { + const targetUrl = String(payload.targetUrl || ""); + const replyContent = String(payload.content || ""); + await win.loadURL(targetUrl); + await this.waitForLoad(win); + await this.delay(2000); + + await win.webContents.executeJavaScript(` + (async () => { + const editor = document.querySelector('[data-testid="tweetTextarea_0"], [role="textbox"]'); + if (editor) { + editor.focus(); + document.execCommand('insertText', false, ${JSON.stringify(replyContent)}); + } + })() + `); + await this.delay(1000); + await win.webContents.executeJavaScript(` + (() => { + const btn = document.querySelector('[data-testid="tweetButton"], [data-testid="tweetButtonInline"], button[type="submit"]'); + if (btn) btn.click(); + })() + `); + await this.delay(3000); + summary = `Replied to ${targetUrl.slice(0, 50)}`; + break; + } + + case "like": { + const targetUrl = String(payload.targetUrl || ""); + await win.loadURL(targetUrl); + await this.waitForLoad(win); + await this.delay(2000); + await win.webContents.executeJavaScript(` + (() => { + const btn = document.querySelector('[data-testid="like"], button[aria-label*="Like"]'); + if (btn) btn.click(); + })() + `); + await this.delay(2000); + summary = `Liked post at ${targetUrl.slice(0, 50)}`; + break; + } + + case "follow": { + const targetUrl = String(payload.targetUrl || payload.profileUrl || ""); + await win.loadURL(targetUrl); + await this.waitForLoad(win); + await this.delay(2000); + await win.webContents.executeJavaScript(` + (() => { + const btn = document.querySelector('[data-testid*="follow"], button[aria-label*="Follow"]'); + if (btn && !btn.textContent.includes('Following')) btn.click(); + })() + `); + await this.delay(2000); + summary = `Followed user at ${targetUrl.slice(0, 50)}`; + break; + } + + case "browse_feed": { + const platform = String(payload.platform || "twitter"); + const feedUrl = String(payload.url || this.getFeedUrl(platform)); + const maxPosts = Number(payload.maxPosts || 10); + await win.loadURL(feedUrl); + await this.waitForLoad(win); + await this.delay(3000); + + // Scroll to load content + for (let i = 0; i < 3; i++) { + await win.webContents.executeJavaScript("window.scrollBy(0, window.innerHeight)"); + await this.delay(1500); + } + + const posts: string[] = await win.webContents.executeJavaScript(` + (() => { + const articles = document.querySelectorAll('article, [data-testid="tweet"], .feed-shared-update-v2'); + return Array.from(articles).slice(0, ${maxPosts}).map(el => el.innerText.slice(0, 300)); + })() + `); + summary = `Browsed feed — found ${posts.length} posts`; + break; + } + + case "search": { + const query = String(payload.query || ""); + const platform = String(payload.platform || "twitter"); + const searchUrl = this.getSearchUrl(platform, query); + await win.loadURL(searchUrl); + await this.waitForLoad(win); + await this.delay(3000); + + const results: string[] = await win.webContents.executeJavaScript(` + (() => { + const items = document.querySelectorAll('article, [data-testid="tweet"], .search-result'); + return Array.from(items).slice(0, 10).map(el => el.innerText.slice(0, 200)); + })() + `); + summary = `Search "${query.slice(0, 30)}" — ${results.length} results`; + break; + } + + case "screenshot": { + const url = String(payload.url || "about:blank"); + if (url !== "about:blank") { + await win.loadURL(url); + await this.waitForLoad(win); + await this.delay(2000); + } + const title = await win.webContents.executeJavaScript("document.title"); + summary = `Screenshot captured: ${title}`; + break; + } + + default: + summary = `Unknown action type: ${action.actionType}`; + } + + // Capture screenshot + try { + const image: NativeImage = await win.webContents.capturePage(); + if (!image.isEmpty()) { + writeFileSync(screenshotPath, image.toPNG()); + } + } catch (err) { + log.warn(`[browser] Screenshot failed: ${err}`); + } + + // Close the action window after a brief pause so user can see result + await this.delay(1000); + if (!win.isDestroyed()) win.close(); + + return { + success: true, + summary, + screenshotPath: existsSync(screenshotPath) ? screenshotPath : null, + error: null, + }; + } catch (error) { + // Try to capture error screenshot + try { + const image: NativeImage = await win.webContents.capturePage(); + if (!image.isEmpty()) writeFileSync(screenshotPath, image.toPNG()); + } catch { /* ignore */ } + + if (!win.isDestroyed()) win.close(); + + const msg = error instanceof Error ? error.message : String(error); + log.error(`[browser] Action ${action.actionType} failed: ${msg}`); + return { + success: false, + summary: "Action failed", + screenshotPath: existsSync(screenshotPath) ? screenshotPath : null, + error: msg, + }; + } + } + + // ── Helpers ────────────────────────────────────────────────────── + + private delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + private waitForLoad(win: BrowserWindow, timeoutMs = 15000): Promise { + return new Promise((resolve) => { + if (win.webContents.isLoading()) { + const timer = setTimeout(resolve, timeoutMs); + win.webContents.once("did-finish-load", () => { + clearTimeout(timer); + resolve(); + }); + } else { + resolve(); + } + }); + } + + private getComposeUrl(platform: string): string { + const urls: Record = { + twitter: "https://x.com/compose/post", + linkedin: "https://www.linkedin.com/feed/", + instagram: "https://www.instagram.com/", + facebook: "https://www.facebook.com/", + reddit: "https://www.reddit.com/submit", + bluesky: "https://bsky.app/", + }; + return urls[platform] || "https://google.com"; + } + + private getFeedUrl(platform: string): string { + const urls: Record = { + twitter: "https://x.com/home", + linkedin: "https://www.linkedin.com/feed/", + instagram: "https://www.instagram.com/", + reddit: "https://www.reddit.com/", + bluesky: "https://bsky.app/", + }; + return urls[platform] || "https://google.com"; + } + + private getSearchUrl(platform: string, query: string): string { + const q = encodeURIComponent(query); + const urls: Record = { + twitter: `https://x.com/search?q=${q}`, + linkedin: `https://www.linkedin.com/search/results/all/?keywords=${q}`, + reddit: `https://www.reddit.com/search/?q=${q}`, + }; + return urls[platform] || `https://google.com/search?q=${q}`; + } + + shutdown() { + // Close any open login windows + for (const [, win] of this.loginWindows) { + if (!win.isDestroyed()) win.close(); + } + this.loginWindows.clear(); + this.queues.clear(); + this.processing.clear(); + } +} diff --git a/src/main/database.ts b/src/main/database.ts index 310f0032..dbfc67e7 100644 --- a/src/main/database.ts +++ b/src/main/database.ts @@ -7,18 +7,25 @@ import { getConnectorDefinition, buildDefaultConnectorRows, parseEnvBindingText import { SecretVault } from "./vault"; import type { ActivityRecord, + AgentMetrics, AgentRecord, ApprovalRecord, + BrowserActionRecord, CommentRecord, + CompanyMetrics, CompanyRecord, ConnectorId, ConnectorRecord, CostRecord, GoalRecord, + InboxItem, ProfileSnapshot, ProjectRecord, RunRecord, + RunStatus, SecretRecord, + SocialAccountRecord, + StandupReport, TaskRecord, WorkspaceRecord, } from "@shared/types"; @@ -430,7 +437,7 @@ export class AppDatabase { "select id, company_id as companyId, name, local_path as localPath, repo_url as repoUrl, repo_ref as repoRef, project_id as projectId, state, is_primary as isPrimary, created_at as createdAt, updated_at as updatedAt from workspaces order by created_at desc", ).map((record) => ({ ...record, isPrimary: toBoolean(record.isPrimary) })) as WorkspaceRecord[], agents: this.readAll( - "select id, company_id as companyId, name, role, title, status, reports_to as reportsTo, connector_id as connectorId, workspace_id as workspaceId, model, capabilities, budget_monthly_usd as budgetMonthlyUsd, spent_monthly_usd as spentMonthlyUsd, metadata_json as metadataJson, heartbeat_enabled as heartbeatEnabled, heartbeat_interval_sec as heartbeatIntervalSec, last_heartbeat_at as lastHeartbeatAt, created_at as createdAt, updated_at as updatedAt from agents order by created_at asc", + "select id, company_id as companyId, name, role, title, department, status, reports_to as reportsTo, connector_id as connectorId, workspace_id as workspaceId, model, capabilities, budget_monthly_usd as budgetMonthlyUsd, spent_monthly_usd as spentMonthlyUsd, metadata_json as metadataJson, heartbeat_enabled as heartbeatEnabled, heartbeat_interval_sec as heartbeatIntervalSec, last_heartbeat_at as lastHeartbeatAt, created_at as createdAt, updated_at as updatedAt from agents order by created_at asc", ).map((record) => ({ ...record, heartbeatEnabled: toBoolean(record.heartbeatEnabled) })) as AgentRecord[], goals: this.readAll( "select id, company_id as companyId, title, description, parent_id as parentId, owner_agent_id as ownerAgentId, status, created_at as createdAt, updated_at as updatedAt from goals order by created_at asc", @@ -459,6 +466,83 @@ export class AppDatabase { comments: this.readAll( "select id, company_id as companyId, task_id as taskId, author_agent_id as authorAgentId, author_name as authorName, body, created_at as createdAt from task_comments order by created_at asc", ), + socialAccounts: this.readAll( + "select id, company_id, platform, account_name, display_name, profile_url, credential_secret_id, status, require_approval, metadata_json, last_used_at, created_at, updated_at from social_accounts order by created_at desc", + ).map((r: any) => ({ + id: r.id, + companyId: r.company_id, + platform: r.platform, + accountName: r.account_name, + displayName: r.display_name, + profileUrl: r.profile_url, + credentialSecretId: r.credential_secret_id, + status: r.status, + requireApproval: toBoolean(r.require_approval), + metadataJson: r.metadata_json, + lastUsedAt: r.last_used_at, + createdAt: r.created_at, + updatedAt: r.updated_at, + })) as SocialAccountRecord[], + browserActions: [], + meetings: this.readAll( + "select id, company_id, type, title, organizer_agent_id, participant_agent_ids, scheduled_at, duration_minutes, agenda_json, notes_json, decisions_json, action_items_json, status, created_at, updated_at from meetings order by scheduled_at desc", + ).map((r: any) => ({ + id: r.id, companyId: r.company_id, type: r.type, title: r.title, organizerAgentId: r.organizer_agent_id, + participantAgentIds: r.participant_agent_ids, scheduledAt: r.scheduled_at, durationMinutes: r.duration_minutes, + agendaJson: r.agenda_json, notesJson: r.notes_json, decisionsJson: r.decisions_json, + actionItemsJson: r.action_items_json, status: r.status, createdAt: r.created_at, updatedAt: r.updated_at, + })), + documents: this.readAll( + "select id, company_id, type, title, content, author_agent_id, reviewer_agent_id, project_id, goal_id, parent_doc_id, version, status, tags_json, created_at, updated_at from documents order by updated_at desc", + ).map((r: any) => ({ + id: r.id, companyId: r.company_id, type: r.type, title: r.title, content: r.content, + authorAgentId: r.author_agent_id, reviewerAgentId: r.reviewer_agent_id, projectId: r.project_id, + goalId: r.goal_id, parentDocId: r.parent_doc_id, version: r.version, status: r.status, + tagsJson: r.tags_json, createdAt: r.created_at, updatedAt: r.updated_at, + })), + knowledgeBase: this.readAll( + "select id, company_id, category, topic, content, author_agent_id, importance, tags_json, referenced_entity_type, referenced_entity_id, created_at, updated_at from knowledge_base order by updated_at desc", + ).map((r: any) => ({ + id: r.id, companyId: r.company_id, category: r.category, topic: r.topic, content: r.content, + authorAgentId: r.author_agent_id, importance: r.importance, tagsJson: r.tags_json, + referencedEntityType: r.referenced_entity_type, referencedEntityId: r.referenced_entity_id, + createdAt: r.created_at, updatedAt: r.updated_at, + })), + sprints: this.readAll( + "select id, company_id, name, goal, start_date, end_date, status, retrospective_notes, velocity_points, completed_points, created_at, updated_at from sprints order by start_date desc", + ).map((r: any) => ({ + id: r.id, companyId: r.company_id, name: r.name, goal: r.goal, + startDate: r.start_date, endDate: r.end_date, status: r.status, + retrospectiveNotes: r.retrospective_notes, velocityPoints: r.velocity_points, + completedPoints: r.completed_points, createdAt: r.created_at, updatedAt: r.updated_at, + })), + automationRules: this.readAll( + "select id, company_id, name, description, trigger, conditions_json, action, action_config_json, source_department, target_department, priority, status, execution_count, last_executed_at, created_at, updated_at from automation_rules order by priority desc, created_at desc", + ).map((r: any) => ({ + id: r.id, companyId: r.company_id, name: r.name, description: r.description, + trigger: r.trigger, conditionsJson: r.conditions_json, action: r.action, + actionConfigJson: r.action_config_json, sourceDepartment: r.source_department, + targetDepartment: r.target_department, priority: r.priority, status: r.status, + executionCount: r.execution_count, lastExecutedAt: r.last_executed_at, + createdAt: r.created_at, updatedAt: r.updated_at, + })), + agentMessages: this.readAll( + "select id, company_id, from_agent_id, to_agent_id, channel, channel_target_id, subject, body, priority, read_at, parent_message_id, attachments_json, created_at from agent_messages order by created_at desc limit 200", + ).map((r: any) => ({ + id: r.id, companyId: r.company_id, fromAgentId: r.from_agent_id, + toAgentId: r.to_agent_id, channel: r.channel, channelTargetId: r.channel_target_id, + subject: r.subject, body: r.body, priority: r.priority, readAt: r.read_at, + parentMessageId: r.parent_message_id, attachmentsJson: r.attachments_json, + createdAt: r.created_at, + })), + workflows: this.readAll( + "select id, company_id, name, description, steps_json, trigger_type, trigger_config_json, status, current_step_index, run_count, last_run_at, created_at, updated_at from workflow_pipelines order by created_at desc", + ).map((r: any) => ({ + id: r.id, companyId: r.company_id, name: r.name, description: r.description, + stepsJson: r.steps_json, triggerType: r.trigger_type, triggerConfigJson: r.trigger_config_json, + status: r.status, currentStepIndex: r.current_step_index, runCount: r.run_count, + lastRunAt: r.last_run_at, createdAt: r.created_at, updatedAt: r.updated_at, + })), }; } @@ -549,6 +633,7 @@ export class AppDatabase { name: string; role: string; title: string; + department?: string | null; status: AgentRecord["status"]; reportsTo?: string | null; connectorId: ConnectorId; @@ -561,6 +646,7 @@ export class AppDatabase { const id = ensureId(input.id ?? null); const ts = nowIso(); const metadataJson = input.metadataJson ?? "{}"; + const department = input.department ?? null; const reportsTo = normalizeOptionalId(input.reportsTo); if (this.wouldCreateAgentCycle(id, reportsTo)) { throw new Error("Agent reporting relationship would create a cycle."); @@ -569,12 +655,13 @@ export class AppDatabase { if (existing) { this.db .prepare( - "update agents set name = ?, role = ?, title = ?, status = ?, reports_to = ?, connector_id = ?, workspace_id = ?, model = ?, metadata_json = ?, capabilities = ?, budget_monthly_usd = ?, updated_at = ? where id = ? and company_id = ?", + "update agents set name = ?, role = ?, title = ?, department = ?, status = ?, reports_to = ?, connector_id = ?, workspace_id = ?, model = ?, metadata_json = ?, capabilities = ?, budget_monthly_usd = ?, updated_at = ? where id = ? and company_id = ?", ) .run( input.name, input.role, input.title, + department, input.status, reportsTo, input.connectorId, @@ -590,7 +677,7 @@ export class AppDatabase { } else { this.db .prepare( - "insert into agents (id, company_id, name, role, title, status, reports_to, connector_id, workspace_id, model, capabilities, budget_monthly_usd, spent_monthly_usd, metadata_json, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?)", + "insert into agents (id, company_id, name, role, title, department, status, reports_to, connector_id, workspace_id, model, capabilities, budget_monthly_usd, spent_monthly_usd, metadata_json, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?)", ) .run( id, @@ -598,6 +685,7 @@ export class AppDatabase { input.name, input.role, input.title, + department, input.status, reportsTo, input.connectorId, @@ -824,7 +912,7 @@ export class AppDatabase { getAgent(id: string): AgentRecord { const row = this.db .prepare( - "select id, company_id as companyId, name, role, title, status, reports_to as reportsTo, connector_id as connectorId, workspace_id as workspaceId, model, capabilities, budget_monthly_usd as budgetMonthlyUsd, spent_monthly_usd as spentMonthlyUsd, metadata_json as metadataJson, heartbeat_enabled as heartbeatEnabled, heartbeat_interval_sec as heartbeatIntervalSec, last_heartbeat_at as lastHeartbeatAt, created_at as createdAt, updated_at as updatedAt from agents where id = ?", + "select id, company_id as companyId, name, role, title, department, status, reports_to as reportsTo, connector_id as connectorId, workspace_id as workspaceId, model, capabilities, budget_monthly_usd as budgetMonthlyUsd, spent_monthly_usd as spentMonthlyUsd, metadata_json as metadataJson, heartbeat_enabled as heartbeatEnabled, heartbeat_interval_sec as heartbeatIntervalSec, last_heartbeat_at as lastHeartbeatAt, created_at as createdAt, updated_at as updatedAt from agents where id = ?", ) .get(id) as AgentRecord | undefined; if (!row) throw new Error(`Agent not found: ${id}`); @@ -1020,6 +1108,15 @@ export class AppDatabase { this.db.prepare("delete from agents where company_id = ?").run(companyId); this.db.prepare("delete from secrets where company_id = ?").run(companyId); this.db.prepare("delete from workspaces where company_id = ?").run(companyId); + // Clean up tables added by migrations (safe to call — migrations run on init) + for (const table of ["automation_rules", "automation_log", "agent_messages", "workflow_pipelines", "meetings", "documents", "knowledge_base", "sprints"]) { + try { + this.db.prepare(`delete from ${table} where company_id = ?`).run(companyId); + } catch (err) { + // Only tolerate "no such table" — re-throw anything else (disk error, etc.) + if (!(err instanceof Error) || !err.message.includes("no such table")) throw err; + } + } this.db.prepare("delete from companies where id = ?").run(companyId); // Switch to another company if the deleted one was current if (this.getCurrentCompanyId() === companyId) { @@ -1104,6 +1201,138 @@ export class AppDatabase { this.db.prepare("delete from secrets where id = ? and company_id = ?").run(id, companyId); } + /* ─── Social Accounts ────────────────────────────────────────────── */ + + saveSocialAccount(input: { + id?: string | null; + companyId: string; + platform: string; + accountName: string; + displayName?: string; + profileUrl?: string; + credentialSecretId?: string | null; + status?: string; + requireApproval?: boolean; + metadataJson?: string; + }): string { + const id = ensureId(input.id); + const now = nowIso(); + this.db.prepare(`INSERT INTO social_accounts (id, company_id, platform, account_name, display_name, profile_url, credential_secret_id, status, require_approval, metadata_json, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + platform = excluded.platform, + account_name = excluded.account_name, + display_name = excluded.display_name, + profile_url = excluded.profile_url, + credential_secret_id = excluded.credential_secret_id, + status = excluded.status, + require_approval = excluded.require_approval, + metadata_json = excluded.metadata_json, + updated_at = excluded.updated_at`).run( + id, input.companyId, input.platform, input.accountName, + input.displayName ?? "", input.profileUrl ?? "", + input.credentialSecretId ?? null, input.status ?? "login_required", + input.requireApproval !== false ? 1 : 0, + input.metadataJson ?? "{}", now, now, + ); + return id; + } + + deleteSocialAccount(id: string, companyId: string) { + this.db.prepare("DELETE FROM social_accounts WHERE id = ? AND company_id = ?").run(id, companyId); + } + + listSocialAccounts(companyId: string): SocialAccountRecord[] { + const rows = this.db.prepare("SELECT * FROM social_accounts WHERE company_id = ? ORDER BY created_at DESC").all(companyId) as any[]; + return rows.map((r: any) => ({ + id: r.id, + companyId: r.company_id, + platform: r.platform, + accountName: r.account_name, + displayName: r.display_name, + profileUrl: r.profile_url, + credentialSecretId: r.credential_secret_id, + status: r.status, + requireApproval: toBoolean(r.require_approval), + metadataJson: r.metadata_json, + lastUsedAt: r.last_used_at, + createdAt: r.created_at, + updatedAt: r.updated_at, + })); + } + + /* ─── Browser Actions ────────────────────────────────────────────── */ + + saveBrowserAction(input: { + companyId: string; + socialAccountId: string; + agentId: string; + taskId?: string | null; + actionType: string; + payloadJson: string; + status?: string; + approvalId?: string | null; + }): string { + const id = randomUUID(); + const now = nowIso(); + this.db.prepare(`INSERT INTO browser_actions (id, company_id, social_account_id, agent_id, task_id, action_type, payload_json, status, approval_id, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run( + id, input.companyId, input.socialAccountId, input.agentId, + input.taskId ?? null, input.actionType, input.payloadJson, + input.status ?? "queued", input.approvalId ?? null, now, now, + ); + return id; + } + + updateBrowserAction(id: string, updates: { status?: string; resultSummary?: string; screenshotPath?: string | null; errorMessage?: string | null; startedAt?: string; finishedAt?: string }) { + const now = nowIso(); + const sets: string[] = ["updated_at = ?"]; + const values: SQLInputValue[] = [now]; + if (updates.status) { sets.push("status = ?"); values.push(updates.status); } + if (updates.resultSummary !== undefined) { sets.push("result_summary = ?"); values.push(updates.resultSummary); } + if (updates.screenshotPath !== undefined) { sets.push("screenshot_path = ?"); values.push(updates.screenshotPath); } + if (updates.errorMessage !== undefined) { sets.push("error_message = ?"); values.push(updates.errorMessage); } + if (updates.startedAt) { sets.push("started_at = ?"); values.push(updates.startedAt); } + if (updates.finishedAt) { sets.push("finished_at = ?"); values.push(updates.finishedAt); } + values.push(id); + this.db.prepare(`UPDATE browser_actions SET ${sets.join(", ")} WHERE id = ?`).run(...values); + } + + listBrowserActions(companyId: string, filters?: { socialAccountId?: string; status?: string; limit?: number }): BrowserActionRecord[] { + let sql = "SELECT * FROM browser_actions WHERE company_id = ?"; + const params: SQLInputValue[] = [companyId]; + if (filters?.socialAccountId) { sql += " AND social_account_id = ?"; params.push(filters.socialAccountId); } + if (filters?.status) { + const statuses = filters.status.split(",").filter(Boolean); + if (statuses.length > 0) { + sql += ` AND status IN (${statuses.map(() => "?").join(",")})`; + params.push(...statuses); + } + } + sql += " ORDER BY created_at DESC"; + if (filters?.limit) { sql += " LIMIT ?"; params.push(filters.limit); } + + const rows = this.db.prepare(sql).all(...params) as any[]; + return rows.map((r: any) => ({ + id: r.id, + companyId: r.company_id, + socialAccountId: r.social_account_id, + agentId: r.agent_id, + taskId: r.task_id, + status: r.status, + actionType: r.action_type, + payloadJson: r.payload_json, + resultSummary: r.result_summary, + screenshotPath: r.screenshot_path, + approvalId: r.approval_id, + errorMessage: r.error_message, + startedAt: r.started_at, + finishedAt: r.finished_at, + createdAt: r.created_at, + updatedAt: r.updated_at, + })); + } + getSidebarBadges(companyId: string): { pendingApprovals: number; activeRuns: number; failedRuns: number; todoTasks: number; budgetWarnings: number } { const pendingApprovals = (this.db.prepare("select count(*) as c from approvals where company_id = ? and state = 'pending'").get(companyId) as { c: number })?.c ?? 0; const activeRuns = (this.db.prepare("select count(*) as c from runs where company_id = ? and status in ('queued', 'running')").get(companyId) as { c: number })?.c ?? 0; @@ -1149,7 +1378,7 @@ export class AppDatabase { listHeartbeatEligibleAgents(companyId: string): AgentRecord[] { return this.readAll( - "select id, company_id as companyId, name, role, title, status, reports_to as reportsTo, connector_id as connectorId, workspace_id as workspaceId, model, capabilities, budget_monthly_usd as budgetMonthlyUsd, spent_monthly_usd as spentMonthlyUsd, metadata_json as metadataJson, heartbeat_enabled as heartbeatEnabled, heartbeat_interval_sec as heartbeatIntervalSec, last_heartbeat_at as lastHeartbeatAt, created_at as createdAt, updated_at as updatedAt from agents where company_id = ? and heartbeat_enabled = 1 and status not in ('terminated', 'paused', 'error', 'pending_approval')", + "select id, company_id as companyId, name, role, title, department, status, reports_to as reportsTo, connector_id as connectorId, workspace_id as workspaceId, model, capabilities, budget_monthly_usd as budgetMonthlyUsd, spent_monthly_usd as spentMonthlyUsd, metadata_json as metadataJson, heartbeat_enabled as heartbeatEnabled, heartbeat_interval_sec as heartbeatIntervalSec, last_heartbeat_at as lastHeartbeatAt, created_at as createdAt, updated_at as updatedAt from agents where company_id = ? and heartbeat_enabled = 1 and status not in ('terminated', 'paused', 'error', 'pending_approval')", companyId, ).map((record) => ({ ...record, heartbeatEnabled: toBoolean(record.heartbeatEnabled) })) as AgentRecord[]; } @@ -1231,7 +1460,7 @@ export class AppDatabase { getDirectReports(agentId: string, companyId: string): AgentRecord[] { return this.readAll( - "select id, company_id as companyId, name, role, title, status, reports_to as reportsTo, connector_id as connectorId, workspace_id as workspaceId, model, capabilities, budget_monthly_usd as budgetMonthlyUsd, spent_monthly_usd as spentMonthlyUsd, metadata_json as metadataJson, heartbeat_enabled as heartbeatEnabled, heartbeat_interval_sec as heartbeatIntervalSec, last_heartbeat_at as lastHeartbeatAt, created_at as createdAt, updated_at as updatedAt from agents where company_id = ? and reports_to = ?", + "select id, company_id as companyId, name, role, title, department, status, reports_to as reportsTo, connector_id as connectorId, workspace_id as workspaceId, model, capabilities, budget_monthly_usd as budgetMonthlyUsd, spent_monthly_usd as spentMonthlyUsd, metadata_json as metadataJson, heartbeat_enabled as heartbeatEnabled, heartbeat_interval_sec as heartbeatIntervalSec, last_heartbeat_at as lastHeartbeatAt, created_at as createdAt, updated_at as updatedAt from agents where company_id = ? and reports_to = ?", companyId, agentId, ).map((record) => ({ ...record, heartbeatEnabled: toBoolean(record.heartbeatEnabled) })) as AgentRecord[]; } @@ -1460,6 +1689,50 @@ export class AppDatabase { create index if not exists idx_comments_task on task_comments(task_id); create index if not exists idx_comments_company on task_comments(company_id); `); + + // Social media accounts table + this.db.exec(` + create table if not exists social_accounts ( + id text primary key, + company_id text not null references companies(id) on delete cascade, + platform text not null default 'other', + account_name text not null, + display_name text not null default '', + profile_url text not null default '', + credential_secret_id text, + status text not null default 'login_required', + require_approval integer not null default 1, + metadata_json text not null default '{}', + last_used_at text, + created_at text not null default (datetime('now')), + updated_at text not null default (datetime('now')) + ); + create index if not exists idx_social_accounts_company on social_accounts(company_id); + `); + + // Browser actions table + this.db.exec(` + create table if not exists browser_actions ( + id text primary key, + company_id text not null references companies(id) on delete cascade, + social_account_id text not null references social_accounts(id) on delete cascade, + agent_id text not null references agents(id) on delete cascade, + task_id text, + action_type text not null default 'custom_script', + payload_json text not null default '{}', + status text not null default 'queued', + result_summary text not null default '', + screenshot_path text, + approval_id text, + error_message text, + started_at text, + finished_at text, + created_at text not null default (datetime('now')), + updated_at text not null default (datetime('now')) + ); + create index if not exists idx_browser_actions_company on browser_actions(company_id); + create index if not exists idx_browser_actions_account on browser_actions(social_account_id); + `); } /** @@ -1587,6 +1860,279 @@ export class AppDatabase { ); } + /* ─── Meetings CRUD ────────────────────────────────────────────── */ + + saveMeeting(input: { + id?: string | null; companyId: string; type: string; title: string; organizerAgentId?: string | null; + participantAgentIds?: string; scheduledAt: string; durationMinutes?: number; + agendaJson?: string; notesJson?: string; decisionsJson?: string; actionItemsJson?: string; + status?: string; + }) { + const id = ensureId(input.id ?? null); + const ts = nowIso(); + const existing = this.db.prepare("select id from meetings where id = ?").get(id) as { id: string } | undefined; + if (existing) { + this.db.prepare( + "update meetings set type = ?, title = ?, organizer_agent_id = ?, participant_agent_ids = ?, scheduled_at = ?, duration_minutes = ?, agenda_json = ?, notes_json = ?, decisions_json = ?, action_items_json = ?, status = ?, updated_at = ? where id = ?", + ).run(input.type, input.title, input.organizerAgentId ?? null, input.participantAgentIds ?? "[]", input.scheduledAt, input.durationMinutes ?? 30, input.agendaJson ?? "[]", input.notesJson ?? "[]", input.decisionsJson ?? "[]", input.actionItemsJson ?? "[]", input.status ?? "scheduled", ts, id); + } else { + this.db.prepare( + "insert into meetings (id, company_id, type, title, organizer_agent_id, participant_agent_ids, scheduled_at, duration_minutes, agenda_json, notes_json, decisions_json, action_items_json, status, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + ).run(id, input.companyId, input.type, input.title, input.organizerAgentId ?? null, input.participantAgentIds ?? "[]", input.scheduledAt, input.durationMinutes ?? 30, input.agendaJson ?? "[]", input.notesJson ?? "[]", input.decisionsJson ?? "[]", input.actionItemsJson ?? "[]", input.status ?? "scheduled", ts, ts); + } + return id; + } + + deleteMeeting(id: string, companyId: string) { + this.db.prepare("delete from meetings where id = ? and company_id = ?").run(id, companyId); + } + + /* ─── Documents CRUD ──────────────────────────────────────────── */ + + saveDocument(input: { + id?: string | null; companyId: string; type: string; title: string; content?: string; + authorAgentId?: string | null; reviewerAgentId?: string | null; projectId?: string | null; + goalId?: string | null; parentDocId?: string | null; version?: number; status?: string; tagsJson?: string; + }) { + const id = ensureId(input.id ?? null); + const ts = nowIso(); + const existing = this.db.prepare("select id from documents where id = ?").get(id) as { id: string } | undefined; + if (existing) { + this.db.prepare( + "update documents set type = ?, title = ?, content = ?, author_agent_id = ?, reviewer_agent_id = ?, project_id = ?, goal_id = ?, parent_doc_id = ?, version = ?, status = ?, tags_json = ?, updated_at = ? where id = ?", + ).run(input.type, input.title, input.content ?? "", input.authorAgentId ?? null, input.reviewerAgentId ?? null, input.projectId ?? null, input.goalId ?? null, input.parentDocId ?? null, input.version ?? 1, input.status ?? "draft", input.tagsJson ?? "[]", ts, id); + } else { + this.db.prepare( + "insert into documents (id, company_id, type, title, content, author_agent_id, reviewer_agent_id, project_id, goal_id, parent_doc_id, version, status, tags_json, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + ).run(id, input.companyId, input.type, input.title, input.content ?? "", input.authorAgentId ?? null, input.reviewerAgentId ?? null, input.projectId ?? null, input.goalId ?? null, input.parentDocId ?? null, input.version ?? 1, input.status ?? "draft", input.tagsJson ?? "[]", ts, ts); + } + return id; + } + + deleteDocument(id: string, companyId: string) { + this.db.prepare("delete from documents where id = ? and company_id = ?").run(id, companyId); + } + + /* ─── Knowledge Base CRUD ─────────────────────────────────────── */ + + saveKnowledgeEntry(input: { + id?: string | null; companyId: string; category: string; topic: string; content: string; + authorAgentId?: string | null; importance?: string; tagsJson?: string; + referencedEntityType?: string | null; referencedEntityId?: string | null; + }) { + const id = ensureId(input.id ?? null); + const ts = nowIso(); + const existing = this.db.prepare("select id from knowledge_base where id = ?").get(id) as { id: string } | undefined; + if (existing) { + this.db.prepare( + "update knowledge_base set category = ?, topic = ?, content = ?, author_agent_id = ?, importance = ?, tags_json = ?, referenced_entity_type = ?, referenced_entity_id = ?, updated_at = ? where id = ?", + ).run(input.category, input.topic, input.content, input.authorAgentId ?? null, input.importance ?? "medium", input.tagsJson ?? "[]", input.referencedEntityType ?? null, input.referencedEntityId ?? null, ts, id); + } else { + this.db.prepare( + "insert into knowledge_base (id, company_id, category, topic, content, author_agent_id, importance, tags_json, referenced_entity_type, referenced_entity_id, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + ).run(id, input.companyId, input.category, input.topic, input.content, input.authorAgentId ?? null, input.importance ?? "medium", input.tagsJson ?? "[]", input.referencedEntityType ?? null, input.referencedEntityId ?? null, ts, ts); + } + return id; + } + + deleteKnowledgeEntry(id: string, companyId: string) { + this.db.prepare("delete from knowledge_base where id = ? and company_id = ?").run(id, companyId); + } + + /* ─── Sprints CRUD ────────────────────────────────────────────── */ + + saveSprint(input: { + id?: string | null; companyId: string; name: string; goal?: string; + startDate: string; endDate: string; status?: string; retrospectiveNotes?: string; + velocityPoints?: number; completedPoints?: number; + }) { + const id = ensureId(input.id ?? null); + const ts = nowIso(); + const existing = this.db.prepare("select id from sprints where id = ?").get(id) as { id: string } | undefined; + if (existing) { + this.db.prepare( + "update sprints set name = ?, goal = ?, start_date = ?, end_date = ?, status = ?, retrospective_notes = ?, velocity_points = ?, completed_points = ?, updated_at = ? where id = ?", + ).run(input.name, input.goal ?? "", input.startDate, input.endDate, input.status ?? "planning", input.retrospectiveNotes ?? "", input.velocityPoints ?? 0, input.completedPoints ?? 0, ts, id); + } else { + this.db.prepare( + "insert into sprints (id, company_id, name, goal, start_date, end_date, status, retrospective_notes, velocity_points, completed_points, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + ).run(id, input.companyId, input.name, input.goal ?? "", input.startDate, input.endDate, input.status ?? "planning", input.retrospectiveNotes ?? "", input.velocityPoints ?? 0, input.completedPoints ?? 0, ts, ts); + } + return id; + } + + deleteSprint(id: string, companyId: string) { + this.db.prepare("delete from sprints where id = ? and company_id = ?").run(id, companyId); + } + + // ── Automation Rules ────────────────────────────────────────────── + + saveAutomationRule(input: { + id?: string | null; companyId: string; name: string; description?: string; + trigger: string; conditionsJson?: string; action: string; actionConfigJson?: string; + sourceDepartment?: string | null; targetDepartment?: string | null; + priority?: number; status?: string; + }) { + const id = ensureId(input.id ?? null); + const ts = nowIso(); + const existing = this.db.prepare("select id from automation_rules where id = ?").get(id) as { id: string } | undefined; + if (existing) { + this.db.prepare( + "update automation_rules set name = ?, description = ?, trigger = ?, conditions_json = ?, action = ?, action_config_json = ?, source_department = ?, target_department = ?, priority = ?, status = ?, updated_at = ? where id = ?", + ).run(input.name, input.description ?? "", input.trigger, input.conditionsJson ?? "{}", input.action, input.actionConfigJson ?? "{}", input.sourceDepartment ?? null, input.targetDepartment ?? null, input.priority ?? 50, input.status ?? "active", ts, id); + } else { + this.db.prepare( + "insert into automation_rules (id, company_id, name, description, trigger, conditions_json, action, action_config_json, source_department, target_department, priority, status, execution_count, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)", + ).run(id, input.companyId, input.name, input.description ?? "", input.trigger, input.conditionsJson ?? "{}", input.action, input.actionConfigJson ?? "{}", input.sourceDepartment ?? null, input.targetDepartment ?? null, input.priority ?? 50, input.status ?? "active", ts, ts); + } + this.addActivity({ companyId: input.companyId, actor: "system", action: existing ? "updated" : "created", entityType: "automation_rule", entityId: id, detail: `${existing ? "Updated" : "Created"} automation rule: ${input.name}` }); + return id; + } + + deleteAutomationRule(id: string, companyId: string) { + this.db.prepare("delete from automation_rules where id = ? and company_id = ?").run(id, companyId); + } + + toggleAutomationRule(id: string, companyId: string, status: "active" | "paused") { + this.db.prepare("update automation_rules set status = ?, updated_at = ? where id = ? and company_id = ?").run(status, nowIso(), id, companyId); + } + + recordAutomationExecution(ruleId: string, companyId: string, ruleName: string, trigger: string, action: string, detail: string) { + const id = randomUUID(); + this.db.prepare("insert into automation_log (id, company_id, rule_id, rule_name, trigger, action, detail, executed_at) values (?, ?, ?, ?, ?, ?, ?, ?)").run(id, companyId, ruleId, ruleName, trigger, action, detail, nowIso()); + this.db.prepare("update automation_rules set execution_count = execution_count + 1, last_executed_at = ? where id = ?").run(nowIso(), ruleId); + } + + getAutomationLog(companyId: string) { + return this.readAll<{ ruleId: string; ruleName: string; trigger: string; action: string; executedAt: string }>( + "select rule_id as ruleId, rule_name as ruleName, trigger, action, executed_at as executedAt from automation_log where company_id = ? order by executed_at desc limit 100", + companyId, + ); + } + + // ── Agent Messages ────────────────────────────────────────────── + + sendAgentMessage(input: { + companyId: string; fromAgentId: string; toAgentId?: string | null; + channel?: string; channelTargetId?: string | null; subject: string; body: string; + priority?: string; parentMessageId?: string | null; attachmentsJson?: string; + }) { + const id = randomUUID(); + this.db.prepare( + "insert into agent_messages (id, company_id, from_agent_id, to_agent_id, channel, channel_target_id, subject, body, priority, parent_message_id, attachments_json, created_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + ).run(id, input.companyId, input.fromAgentId, input.toAgentId ?? null, input.channel ?? "direct", input.channelTargetId ?? null, input.subject, input.body, input.priority ?? "normal", input.parentMessageId ?? null, input.attachmentsJson ?? "[]", nowIso()); + this.addActivity({ companyId: input.companyId, actor: input.fromAgentId, action: "sent_message", entityType: "agent_message", entityId: id, detail: `Message: ${input.subject}` }); + return id; + } + + listAgentMessages(companyId: string, opts?: { agentId?: string; channel?: string; unreadOnly?: boolean; limit?: number }) { + let sql = "select id, company_id as companyId, from_agent_id as fromAgentId, to_agent_id as toAgentId, channel, channel_target_id as channelTargetId, subject, body, priority, read_at as readAt, parent_message_id as parentMessageId, attachments_json as attachmentsJson, created_at as createdAt from agent_messages where company_id = ?"; + const params: SQLInputValue[] = [companyId]; + if (opts?.agentId) { sql += " and (to_agent_id = ? or from_agent_id = ? or to_agent_id is null)"; params.push(opts.agentId, opts.agentId); } + if (opts?.channel) { sql += " and channel = ?"; params.push(opts.channel); } + if (opts?.unreadOnly) { sql += " and read_at is null"; } + sql += ` order by created_at desc limit ${opts?.limit ?? 50}`; + return this.db.prepare(sql).all(...params) as any[]; + } + + markMessageRead(messageId: string, companyId: string) { + this.db.prepare("update agent_messages set read_at = ? where id = ? and company_id = ?").run(nowIso(), messageId, companyId); + } + + // ── Workflow Pipelines ──────────────────────────────────────────── + + saveWorkflow(input: { + id?: string | null; companyId: string; name: string; description?: string; + stepsJson?: string; triggerType: string; triggerConfigJson?: string; status?: string; + }) { + const id = ensureId(input.id ?? null); + const ts = nowIso(); + const existing = this.db.prepare("select id from workflow_pipelines where id = ?").get(id) as { id: string } | undefined; + if (existing) { + this.db.prepare( + "update workflow_pipelines set name = ?, description = ?, steps_json = ?, trigger_type = ?, trigger_config_json = ?, status = ?, updated_at = ? where id = ?", + ).run(input.name, input.description ?? "", input.stepsJson ?? "[]", input.triggerType, input.triggerConfigJson ?? "{}", input.status ?? "draft", ts, id); + } else { + this.db.prepare( + "insert into workflow_pipelines (id, company_id, name, description, steps_json, trigger_type, trigger_config_json, status, current_step_index, run_count, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?)", + ).run(id, input.companyId, input.name, input.description ?? "", input.stepsJson ?? "[]", input.triggerType, input.triggerConfigJson ?? "{}", input.status ?? "draft", ts, ts); + } + this.addActivity({ companyId: input.companyId, actor: "system", action: existing ? "updated" : "created", entityType: "workflow", entityId: id, detail: `${existing ? "Updated" : "Created"} workflow: ${input.name}` }); + return id; + } + + deleteWorkflow(id: string, companyId: string) { + this.db.prepare("delete from workflow_pipelines where id = ? and company_id = ?").run(id, companyId); + } + + activateWorkflow(id: string, companyId: string) { + const row = this.db.prepare("select name, status from workflow_pipelines where id = ? and company_id = ?").get(id, companyId) as { name: string; status: string } | undefined; + if (!row) throw new Error(`Workflow not found: ${id}`); + if (row.status === "active") throw new Error("Workflow is already running."); + const ts = nowIso(); + this.db.prepare( + "update workflow_pipelines set status = 'active', current_step_index = 0, run_count = run_count + 1, last_run_at = ?, updated_at = ? where id = ?", + ).run(ts, ts, id); + this.addActivity({ companyId, actor: "system", action: "started", entityType: "workflow", entityId: id, detail: `Started workflow: ${row.name}` }); + } + + // ── Auto Task Assignment ────────────────────────────────────────── + + autoAssignTask(taskId: string, companyId: string): string | null { + const task = this.db.prepare( + "select id, title, description, project_id, assignee_agent_id from tasks where id = ? and company_id = ?", + ).get(taskId, companyId) as { id: string; title: string; description: string; project_id: string | null; assignee_agent_id: string | null } | undefined; + if (!task) return null; + if (task.assignee_agent_id) return task.assignee_agent_id; + + // Find available agents (active/idle with remaining budget, exclude managers with direct reports) + const agents = this.readAll<{ id: string; department: string | null; capabilities: string; budget_monthly_usd: number; spent_monthly_usd: number }>( + "select id, department, capabilities, budget_monthly_usd, spent_monthly_usd from agents where company_id = ? and status in ('idle', 'active') and budget_monthly_usd > spent_monthly_usd", + companyId, + ); + if (agents.length === 0) return null; + + // Extract keywords from task title + description for capability matching + const taskText = `${task.title} ${task.description}`.toLowerCase(); + const taskKeywords = taskText.split(/\W+/).filter(w => w.length > 3); + + let bestAgent: typeof agents[0] | null = null; + let bestScore = -Infinity; + + for (const agent of agents) { + // Workload: fewer active tasks = higher score + const activeTasks = this.readAll<{ cnt: number }>( + "select count(*) as cnt from tasks where assignee_agent_id = ? and status in ('todo', 'in_progress')", + agent.id, + )[0]?.cnt ?? 0; + + // Budget: more remaining = higher score + const budgetRemaining = agent.budget_monthly_usd - agent.spent_monthly_usd; + + // Capability match: count overlapping keywords between task and agent capabilities + const capText = (agent.capabilities ?? "").toLowerCase(); + let capMatchCount = 0; + for (const kw of taskKeywords) { + if (capText.includes(kw)) capMatchCount++; + } + + // Composite score: capability match (strongest signal), then workload, then budget + const score = (capMatchCount * 100) + budgetRemaining - (activeTasks * 50); + + if (score > bestScore) { + bestScore = score; + bestAgent = agent; + } + } + + if (bestAgent) { + this.db.prepare("update tasks set assignee_agent_id = ?, updated_at = ? where id = ?").run(bestAgent.id, nowIso(), taskId); + this.addActivity({ companyId, actor: "system", action: "auto_assigned", entityType: "task", entityId: taskId, detail: `Auto-assigned to ${bestAgent.id} (score: ${bestScore})` }); + return bestAgent.id; + } + return null; + } + /* ─── Forward-Only Migration System ──────────────────────────────── */ private runMigrations() { @@ -1639,6 +2185,180 @@ export class AppDatabase { // and renderer all treat monetary values as dollars. Converting stored data to // cents without updating every consumer would cause 100x display errors. // Dollar float values are sufficient for a local-first desktop app. + { + version: 6, + name: "add_agent_department", + sql: `alter table agents add column department text`, + }, + { + version: 7, + name: "add_meetings_table", + sql: ` + create table if not exists meetings ( + id text primary key, + company_id text not null, + type text not null, + title text not null, + organizer_agent_id text, + participant_agent_ids text not null default '[]', + scheduled_at text not null, + duration_minutes integer not null default 30, + agenda_json text not null default '[]', + notes_json text not null default '[]', + decisions_json text not null default '[]', + action_items_json text not null default '[]', + status text not null default 'scheduled', + created_at text not null, + updated_at text not null + ); + create index if not exists idx_meetings_company on meetings(company_id); + create index if not exists idx_meetings_organizer on meetings(organizer_agent_id) + `, + }, + { + version: 8, + name: "add_documents_table", + sql: ` + create table if not exists documents ( + id text primary key, + company_id text not null, + type text not null, + title text not null, + content text not null default '', + author_agent_id text, + reviewer_agent_id text, + project_id text, + goal_id text, + parent_doc_id text, + version integer not null default 1, + status text not null default 'draft', + tags_json text not null default '[]', + created_at text not null, + updated_at text not null + ); + create index if not exists idx_documents_company on documents(company_id); + create index if not exists idx_documents_author on documents(author_agent_id); + create index if not exists idx_documents_project on documents(project_id) + `, + }, + { + version: 9, + name: "add_knowledge_base_table", + sql: ` + create table if not exists knowledge_base ( + id text primary key, + company_id text not null, + category text not null, + topic text not null, + content text not null, + author_agent_id text, + importance text not null default 'medium', + tags_json text not null default '[]', + referenced_entity_type text, + referenced_entity_id text, + created_at text not null, + updated_at text not null + ); + create index if not exists idx_knowledge_company on knowledge_base(company_id); + create index if not exists idx_knowledge_category on knowledge_base(category) + `, + }, + { + version: 10, + name: "add_sprints_table", + sql: ` + create table if not exists sprints ( + id text primary key, + company_id text not null, + name text not null, + goal text not null default '', + start_date text not null, + end_date text not null, + status text not null default 'planning', + retrospective_notes text not null default '', + velocity_points integer not null default 0, + completed_points integer not null default 0, + created_at text not null, + updated_at text not null + ); + create index if not exists idx_sprints_company on sprints(company_id) + `, + }, + { + version: 11, + name: "add_automation_messages_workflows", + sql: ` + create table if not exists automation_rules ( + id text primary key, + company_id text not null, + name text not null, + description text not null default '', + trigger text not null, + conditions_json text not null default '{}', + action text not null, + action_config_json text not null default '{}', + source_department text, + target_department text, + priority integer not null default 50, + status text not null default 'active', + execution_count integer not null default 0, + last_executed_at text, + created_at text not null, + updated_at text not null + ); + create index if not exists idx_automation_rules_company on automation_rules(company_id); + create index if not exists idx_automation_rules_trigger on automation_rules(trigger); + + create table if not exists agent_messages ( + id text primary key, + company_id text not null, + from_agent_id text not null, + to_agent_id text, + channel text not null default 'direct', + channel_target_id text, + subject text not null, + body text not null, + priority text not null default 'normal', + read_at text, + parent_message_id text, + attachments_json text not null default '[]', + created_at text not null + ); + create index if not exists idx_agent_messages_company on agent_messages(company_id); + create index if not exists idx_agent_messages_to on agent_messages(to_agent_id); + create index if not exists idx_agent_messages_from on agent_messages(from_agent_id); + create index if not exists idx_agent_messages_channel on agent_messages(channel); + + create table if not exists workflow_pipelines ( + id text primary key, + company_id text not null, + name text not null, + description text not null default '', + steps_json text not null default '[]', + trigger_type text not null, + trigger_config_json text not null default '{}', + status text not null default 'draft', + current_step_index integer not null default 0, + run_count integer not null default 0, + last_run_at text, + created_at text not null, + updated_at text not null + ); + create index if not exists idx_workflows_company on workflow_pipelines(company_id); + + create table if not exists automation_log ( + id text primary key, + company_id text not null, + rule_id text not null, + rule_name text not null, + trigger text not null, + action text not null, + detail text not null default '', + executed_at text not null + ); + create index if not exists idx_automation_log_company on automation_log(company_id) + `, + }, ]; for (const migration of migrations) { @@ -1761,6 +2481,270 @@ export class AppDatabase { this.init(); } + // ── Inbox ────────────────────────────────────────────────────────── + getInbox(companyId: string): InboxItem[] { + const items: InboxItem[] = []; + const snapshot = this.listSnapshot(); + const agents = snapshot.agents.filter(a => a.companyId === companyId); + const agentMap = new Map(agents.map(a => [a.id, a])); + + // Pending approvals + for (const approval of snapshot.approvals.filter(a => a.companyId === companyId && a.state === "pending")) { + const requester = agentMap.get(approval.requestedByAgentId ?? ""); + items.push({ + id: `approval-${approval.id}`, + type: "pending_approval", + title: approval.payloadSummary, + subtitle: `${approval.type.replaceAll("_", " ")} — requested by ${requester?.name ?? "Unknown"}`, + severity: approval.type === "dangerous_command" ? "critical" : "high", + entityType: "approval", + entityId: approval.id, + agentId: approval.requestedByAgentId, + agentName: requester?.name ?? null, + createdAt: approval.createdAt, + dismissed: false, + }); + } + + // Failed runs (last 24h) + const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); + for (const run of snapshot.runs.filter(r => r.companyId === companyId && (r.status === "failed" || r.status === "timed_out") && r.createdAt > oneDayAgo)) { + const agent = agentMap.get(run.agentId); + items.push({ + id: `run-${run.id}`, + type: "failed_run", + title: run.summary || "Run failed", + subtitle: `${agent?.name ?? "Unknown agent"} — ${run.errorMessage?.slice(0, 100) ?? "Unknown error"}`, + severity: "high", + entityType: "run", + entityId: run.id, + agentId: run.agentId, + agentName: agent?.name ?? null, + createdAt: run.createdAt, + dismissed: false, + }); + } + + // Budget warnings (agents at >80% budget) + for (const agent of agents.filter(a => a.budgetMonthlyUsd > 0 && a.spentMonthlyUsd / a.budgetMonthlyUsd > 0.8)) { + const pct = Math.round((agent.spentMonthlyUsd / agent.budgetMonthlyUsd) * 100); + items.push({ + id: `budget-${agent.id}`, + type: "budget_warning", + title: `${agent.name} at ${pct}% budget`, + subtitle: `$${agent.spentMonthlyUsd.toFixed(2)} of $${agent.budgetMonthlyUsd.toFixed(2)} monthly limit`, + severity: pct >= 100 ? "critical" : "high", + entityType: "agent", + entityId: agent.id, + agentId: agent.id, + agentName: agent.name, + createdAt: agent.updatedAt, + dismissed: false, + }); + } + + // Blocked tasks + for (const task of snapshot.tasks.filter(t => t.companyId === companyId && t.status === "blocked")) { + const assignee = agentMap.get(task.assigneeAgentId ?? ""); + items.push({ + id: `blocked-${task.id}`, + type: "blocked_task", + title: task.title, + subtitle: `Assigned to ${assignee?.name ?? "Unassigned"} — blocked`, + severity: task.priority === "critical" ? "critical" : "medium", + entityType: "task", + entityId: task.id, + agentId: task.assigneeAgentId, + agentName: assignee?.name ?? null, + createdAt: task.updatedAt, + dismissed: false, + }); + } + + // Tasks needing review + for (const task of snapshot.tasks.filter(t => t.companyId === companyId && t.status === "in_review")) { + const assignee = agentMap.get(task.assigneeAgentId ?? ""); + items.push({ + id: `review-${task.id}`, + type: "review_needed", + title: task.title, + subtitle: `Submitted by ${assignee?.name ?? "Unknown"} — awaiting review`, + severity: task.priority === "critical" ? "high" : "medium", + entityType: "task", + entityId: task.id, + agentId: task.assigneeAgentId, + agentName: assignee?.name ?? null, + createdAt: task.updatedAt, + dismissed: false, + }); + } + + // Sort by severity then date + const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 }; + items.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity] || new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + return items; + } + + // ── Company Metrics ──────────────────────────────────────────────── + getCompanyMetrics(companyId: string, days: number = 30): CompanyMetrics { + const snapshot = this.listSnapshot(); + const agents = snapshot.agents.filter(a => a.companyId === companyId); + const tasks = snapshot.tasks.filter(t => t.companyId === companyId); + const runs = snapshot.runs.filter(r => r.companyId === companyId); + const costs = snapshot.costs.filter(c => c.companyId === companyId); + + const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); + const recentRuns = runs.filter(r => r.createdAt > cutoff); + const recentTasks = tasks.filter(t => t.status === "done" && t.updatedAt > cutoff); + + const successfulRuns = recentRuns.filter(r => r.status === "succeeded").length; + const failedRuns = recentRuns.filter(r => r.status === "failed" || r.status === "timed_out").length; + const totalCost = costs.reduce((sum, c) => sum + (c.amountUsd ?? 0), 0); + const completedTasks = tasks.filter(t => t.status === "done").length; + + const agentMetrics: AgentMetrics[] = agents + .filter(a => a.status !== "pending_approval" && a.status !== "terminated") + .map(agent => { + const agentTasks = tasks.filter(t => t.assigneeAgentId === agent.id); + const agentRuns = runs.filter(r => r.agentId === agent.id); + const agentRecentRuns = agentRuns.filter(r => r.createdAt > cutoff); + const agentCosts = costs.filter(c => c.agentId === agent.id); + + const agentSucceeded = agentRecentRuns.filter(r => r.status === "succeeded").length; + const agentFailed = agentRecentRuns.filter(r => r.status === "failed" || r.status === "timed_out").length; + + // Avg run duration + const finishedRuns = agentRuns.filter(r => r.startedAt && r.finishedAt); + const avgDuration = finishedRuns.length > 0 + ? finishedRuns.reduce((sum, r) => sum + (new Date(r.finishedAt!).getTime() - new Date(r.startedAt!).getTime()) / 1000, 0) / finishedRuns.length + : 0; + + // Review cycles: count tasks that went from in_review back to in_progress + // Approximation: count activity entries with action "task.status_changed" and detail containing "in_progress" + const reviewCycles = snapshot.activity.filter( + a => a.companyId === companyId && a.actor === agent.id && a.action === "task.status_changed" + ).length; + + const lastRun = agentRuns.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0]; + + return { + agentId: agent.id, + agentName: agent.name, + tasksCompleted: agentTasks.filter(t => t.status === "done").length, + tasksAssigned: agentTasks.filter(t => t.status !== "done" && t.status !== "cancelled").length, + tasksFailed: agentTasks.filter(t => t.status === "cancelled").length, + successRate: agentRecentRuns.length > 0 ? Math.round((agentSucceeded / agentRecentRuns.length) * 100) : 0, + avgRunDurationSec: Math.round(avgDuration), + totalCostUsd: agentCosts.reduce((sum, c) => sum + (c.amountUsd ?? 0), 0), + runsTotal: agentRecentRuns.length, + runsSucceeded: agentSucceeded, + runsFailed: agentFailed, + reviewCycles, + lastActiveAt: lastRun?.createdAt ?? null, + }; + }); + + return { + totalAgents: agents.filter(a => a.status !== "pending_approval" && a.status !== "terminated").length, + activeAgents: agents.filter(a => a.status === "active" || a.status === "running").length, + totalTasks: tasks.length, + completedTasks, + blockedTasks: tasks.filter(t => t.status === "blocked").length, + totalRuns: recentRuns.length, + successfulRuns, + failedRuns, + overallSuccessRate: recentRuns.length > 0 ? Math.round((successfulRuns / recentRuns.length) * 100) : 0, + totalCostUsd: totalCost, + avgCostPerTask: completedTasks > 0 ? totalCost / completedTasks : 0, + throughputTasksPerDay: days > 0 ? recentTasks.length / days : 0, + agentMetrics, + }; + } + + // ── Standup Report ───────────────────────────────────────────────── + getStandupReport(companyId: string): StandupReport { + const snapshot = this.listSnapshot(); + const agents = snapshot.agents.filter(a => a.companyId === companyId && a.status !== "terminated" && a.status !== "pending_approval"); + const tasks = snapshot.tasks.filter(t => t.companyId === companyId); + const runs = snapshot.runs.filter(r => r.companyId === companyId); + const costs = snapshot.costs.filter(c => c.companyId === companyId); + + const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); + const recentActivity = snapshot.activity.filter(a => a.companyId === companyId && a.createdAt > oneDayAgo); + + const companyHighlights: string[] = []; + const blockers: string[] = []; + + const recentCompletedTasks = tasks.filter(t => t.status === "done" && t.updatedAt > oneDayAgo); + if (recentCompletedTasks.length > 0) { + companyHighlights.push(`${recentCompletedTasks.length} task(s) completed in last 24h`); + } + + const recentRuns = runs.filter(r => r.createdAt > oneDayAgo); + const successfulRuns = recentRuns.filter(r => r.status === "succeeded").length; + const failedRuns = recentRuns.filter(r => r.status === "failed" || r.status === "timed_out").length; + if (recentRuns.length > 0) { + companyHighlights.push(`${recentRuns.length} runs (${successfulRuns} succeeded, ${failedRuns} failed)`); + } + + const blockedTasks = tasks.filter(t => t.status === "blocked"); + for (const task of blockedTasks) { + const assignee = agents.find(a => a.id === task.assigneeAgentId); + blockers.push(`${task.title} — blocked (${assignee?.name ?? "unassigned"})`); + } + + const pendingApprovals = snapshot.approvals.filter(a => a.companyId === companyId && a.state === "pending").length; + const totalBudget = agents.reduce((sum, a) => sum + a.budgetMonthlyUsd, 0); + const totalSpent = agents.reduce((sum, a) => sum + a.spentMonthlyUsd, 0); + const budgetUtilization = totalBudget > 0 ? Math.round((totalSpent / totalBudget) * 100) : 0; + + const agentReports = agents.map(agent => { + const agentTasks = tasks.filter(t => t.assigneeAgentId === agent.id); + const agentRuns = runs.filter(r => r.agentId === agent.id); + const agentRecentRuns = agentRuns.filter(r => r.createdAt > oneDayAgo); + const agentCosts = costs.filter(c => c.agentId === agent.id && c.createdAt > oneDayAgo); + const lastRun = agentRecentRuns.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0]; + + const highlights: string[] = []; + const completedRecently = agentTasks.filter(t => t.status === "done" && t.updatedAt > oneDayAgo); + if (completedRecently.length > 0) highlights.push(`Completed ${completedRecently.length} task(s)`); + if (agentRecentRuns.length > 0) highlights.push(`${agentRecentRuns.length} run(s) in last 24h`); + const reviewedTasks = recentActivity.filter(a => a.actor === agent.id && a.action.includes("status_changed")).length; + if (reviewedTasks > 0) highlights.push(`${reviewedTasks} status update(s)`); + + return { + agentId: agent.id, + agentName: agent.name, + role: agent.role, + status: agent.status, + tasksInProgress: agentTasks.filter(t => t.status === "in_progress").length, + tasksCompleted24h: completedRecently.length, + tasksBlocked: agentTasks.filter(t => t.status === "blocked").length, + lastRunStatus: (lastRun?.status ?? null) as RunStatus | null, + lastRunAt: lastRun?.createdAt ?? null, + costLast24h: agentCosts.reduce((sum, c) => sum + (c.amountUsd ?? 0), 0), + highlights, + }; + }); + + const summaryParts: string[] = []; + if (agents.length > 0) summaryParts.push(`${agents.length} active agents`); + summaryParts.push(`${recentCompletedTasks.length} tasks done`); + if (blockedTasks.length > 0) summaryParts.push(`${blockedTasks.length} blocked`); + if (pendingApprovals > 0) summaryParts.push(`${pendingApprovals} pending approvals`); + + return { + companyId, + generatedAt: new Date().toISOString(), + summary: summaryParts.join(" · "), + agentReports, + companyHighlights, + blockers, + pendingApprovals, + budgetUtilization, + }; + } + private openConnection(): SqliteDatabase { return new DatabaseSync(this.dbPath); } diff --git a/src/main/index.ts b/src/main/index.ts index 61aaa4f2..a6206824 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -26,6 +26,7 @@ import { approvalDecisionSchema, approvalInputSchema, agentInputSchema, + browserActionQuerySchema, cancelRunSchema, commentInputSchema, companyInputSchema, @@ -33,16 +34,29 @@ import { deleteEntitySchema, goalInputSchema, heartbeatSettingsSchema, + inboxDismissSchema, listCommentsSchema, + metricsQuerySchema, type DesktopResult, projectInputSchema, runLogSchema, secretInputSchema, settingsSchema, + socialAccountInputSchema, startRunSchema, taskInputSchema, + templateApplySchema, triggerHeartbeatSchema, workspaceInputSchema, + meetingInputSchema, + documentInputSchema, + knowledgeEntryInputSchema, + sprintInputSchema, + automationRuleInputSchema, + agentMessageInputSchema, + messageQuerySchema, + workflowInputSchema, + autoAssignSchema, } from "@shared/contracts"; import type { DesktopEvent } from "@shared/types"; import { getConnectorDefinition } from "./connectors"; @@ -50,6 +64,8 @@ import { AppDatabase } from "./database"; import { AgentApiServer } from "./api-server"; import { createAgentToken } from "./agent-auth"; import { buildTaskPrompt } from "./prompt-builder"; +import { COMPANY_TEMPLATES, getTemplate } from "./templates"; +import { BrowserManager } from "./browser-manager"; app.name = "AgentCompany"; @@ -105,6 +121,7 @@ let mainWindow: BrowserWindow | null = null; let db: AppDatabase; let worker: ReturnType | null = null; let apiServer: AgentApiServer | null = null; +let browserManager: BrowserManager | null = null; let heartbeatTimer: ReturnType | null = null; let isQuitting = false; let backgroundCloseNoticeShown = false; @@ -304,17 +321,32 @@ function buildMenu() { Menu.setApplicationMenu(Menu.buildFromTemplate(template)); } +function isSocialBrowserSession(contents: Electron.WebContents): boolean { + // Social login windows use partitioned sessions named "persist:social-..." + const partition = (contents.session as { getStoragePath?: () => string })?.getStoragePath?.() ?? ""; + // Simpler: check the session object reference — social sessions are never the default session + return contents.session !== session.defaultSession; +} + function installSecurityGuards() { session.defaultSession.setPermissionCheckHandler(() => false); session.defaultSession.setPermissionRequestHandler((_webContents, _permission, callback) => { callback(false); }); app.on("web-contents-created", (_event, contents) => { - contents.setWindowOpenHandler(() => ({ action: "deny" })); + contents.setWindowOpenHandler(({ url }) => { + // Allow social browser windows to open popups (OAuth flows etc.) + if (isSocialBrowserSession(contents)) { + return { action: "allow" }; + } + return { action: "deny" }; + }); contents.on("will-attach-webview", (event) => { event.preventDefault(); }); contents.on("will-navigate", (event) => { + // Allow navigation in social login browser windows + if (isSocialBrowserSession(contents)) return; event.preventDefault(); }); }); @@ -464,13 +496,26 @@ function createWorker() { const recoveryApiUrl = recoveryApiPort ? `http://127.0.0.1:${recoveryApiPort}` : ""; const recoveryApiKey = recoveryApiUrl ? createAgentToken(agent.id, candidate.companyId, candidate.id) : ""; + const recoveryAgentTasks = recoverySnapshot.tasks.filter((t) => t.companyId === candidate.companyId && t.assigneeAgentId === agent.id); + const recoveryCompany = recoverySnapshot.companies.find((c) => c.id === candidate.companyId); + const recoveryRecentComments = recoverySnapshot.comments + .filter((c) => recoveryAgentTasks.some((t) => t.id === c.taskId)) + .sort((a, b) => b.createdAt.localeCompare(a.createdAt)) + .slice(0, 20) + .map((c) => ({ + taskId: c.taskId, + taskTitle: recoveryAgentTasks.find((t) => t.id === c.taskId)?.title ?? c.taskId, + authorName: c.authorName, + body: c.body, + createdAt: c.createdAt, + })); const recoveryPrompt = recoveryApiUrl ? buildTaskPrompt({ agent, apiUrl: recoveryApiUrl, apiKey: recoveryApiKey, runId: candidate.id, task, - companyName: recoverySnapshot.companies.find((c) => c.id === candidate.companyId)?.name ?? "Company", + companyName: recoveryCompany?.name ?? "Company", goals: recoverySnapshot.goals.filter((g) => g.companyId === candidate.companyId), projects: recoverySnapshot.projects.filter((p) => p.companyId === candidate.companyId), directReports: db.getDirectReports(agent.id, candidate.companyId), @@ -484,6 +529,30 @@ function createWorker() { ), tasksAwaitingReview: db.listReviewTasksForManager(agent.id, candidate.companyId), wakeReason: "recovery", + allTasks: recoverySnapshot.tasks.filter((t) => t.companyId === candidate.companyId), + recentComments: recoveryRecentComments, + companyDescription: recoveryCompany?.description ?? "", + socialAccounts: db.listSocialAccounts(candidate.companyId).map(a => ({ + id: a.id, + platform: a.platform, + accountName: a.accountName, + status: a.status, + requireApproval: a.requireApproval, + })), + recentMessages: db.listAgentMessages(candidate.companyId, { agentId: agent.id, limit: 15 }), + upcomingMeetings: recoverySnapshot.meetings + .filter((m: any) => m.companyId === candidate.companyId && (m.status === "scheduled" || m.status === "in_progress")) + .filter((m: any) => { + try { const ids = JSON.parse(m.participantAgentIds); return ids.includes(agent.id) || m.organizerAgentId === agent.id; } catch { return false; } + }) + .slice(0, 5), + relevantKnowledge: recoverySnapshot.knowledgeBase + .filter((k: any) => k.companyId === candidate.companyId && (k.importance === "critical" || k.importance === "high")) + .slice(0, 8), + activeSprint: recoverySnapshot.sprints.find((s: any) => s.companyId === candidate.companyId && s.status === "active") ?? null, + recentDocuments: recoverySnapshot.documents + .filter((d: any) => d.companyId === candidate.companyId && (d.authorAgentId === agent.id || d.reviewerAgentId === agent.id || d.status === "in_review")) + .slice(0, 5), }) : null; // Inject API credentials exactly like the normal path @@ -935,6 +1004,17 @@ function startAgentHeartbeatRun(agentId: string, companyId: string, trigger: str const apiUrl = apiPort ? `http://127.0.0.1:${apiPort}` : ""; const apiKey = apiUrl ? createAgentToken(agent.id, companyId, run.id) : ""; + const heartbeatRecentComments = snapshot.comments + .filter((c) => assignedTasks.some((t) => t.id === c.taskId)) + .sort((a, b) => b.createdAt.localeCompare(a.createdAt)) + .slice(0, 20) + .map((c) => ({ + taskId: c.taskId, + taskTitle: assignedTasks.find((t) => t.id === c.taskId)?.title ?? c.taskId, + authorName: c.authorName, + body: c.body, + createdAt: c.createdAt, + })); const systemPrompt = apiUrl ? buildTaskPrompt({ agent, apiUrl, @@ -955,6 +1035,30 @@ function startAgentHeartbeatRun(agentId: string, companyId: string, trigger: str ), tasksAwaitingReview: db.listReviewTasksForManager(agent.id, companyId), wakeReason: trigger, + allTasks: snapshot.tasks.filter((t) => t.companyId === companyId), + recentComments: heartbeatRecentComments, + companyDescription: company.description ?? "", + socialAccounts: db.listSocialAccounts(companyId).map(a => ({ + id: a.id, + platform: a.platform, + accountName: a.accountName, + status: a.status, + requireApproval: a.requireApproval, + })), + recentMessages: db.listAgentMessages(companyId, { agentId: agent.id, limit: 15 }), + upcomingMeetings: snapshot.meetings + .filter((m: any) => m.companyId === companyId && (m.status === "scheduled" || m.status === "in_progress")) + .filter((m: any) => { + try { const ids = JSON.parse(m.participantAgentIds); return ids.includes(agent.id) || m.organizerAgentId === agent.id; } catch { return false; } + }) + .slice(0, 5), + relevantKnowledge: snapshot.knowledgeBase + .filter((k: any) => k.companyId === companyId && (k.importance === "critical" || k.importance === "high")) + .slice(0, 8), + activeSprint: snapshot.sprints.find((s: any) => s.companyId === companyId && s.status === "active") ?? null, + recentDocuments: snapshot.documents + .filter((d: any) => d.companyId === companyId && (d.authorAgentId === agent.id || d.reviewerAgentId === agent.id || d.status === "in_review")) + .slice(0, 5), }) : null; // Add API credentials to env @@ -1468,18 +1572,31 @@ function registerHandlers() { const apiUrl = apiPort ? `http://127.0.0.1:${apiPort}` : ""; const apiKey = apiUrl ? createAgentToken(agent.id, task.companyId, run.id) : ""; + const manualAgentTasks = db.listAgentTasks(agent.id, task.companyId, ["todo", "in_progress", "blocked"]); + const manualCompany = snapshot.companies.find((c) => c.id === task.companyId); + const manualRecentComments = snapshot.comments + .filter((c) => manualAgentTasks.some((t) => t.id === c.taskId)) + .sort((a, b) => b.createdAt.localeCompare(a.createdAt)) + .slice(0, 20) + .map((c) => ({ + taskId: c.taskId, + taskTitle: manualAgentTasks.find((t) => t.id === c.taskId)?.title ?? c.taskId, + authorName: c.authorName, + body: c.body, + createdAt: c.createdAt, + })); const systemPrompt = apiUrl ? buildTaskPrompt({ agent, apiUrl, apiKey, runId: run.id, task, - companyName: snapshot.companies.find((c) => c.id === task.companyId)?.name ?? "Company", + companyName: manualCompany?.name ?? "Company", goals: snapshot.goals.filter((g) => g.companyId === task.companyId), projects: snapshot.projects.filter((p) => p.companyId === task.companyId), directReports: db.getDirectReports(agent.id, task.companyId), chainOfCommand: db.getChainOfCommand(agent.id), - assignedTasks: db.listAgentTasks(agent.id, task.companyId, ["todo", "in_progress", "blocked"]), + assignedTasks: manualAgentTasks, allAgents: snapshot.agents.filter((a) => a.companyId === task.companyId), pendingApprovals: snapshot.approvals.filter( (a) => a.companyId === task.companyId && @@ -1488,6 +1605,30 @@ function registerHandlers() { ), tasksAwaitingReview: db.listReviewTasksForManager(agent.id, task.companyId), wakeReason: "manual", + allTasks: snapshot.tasks.filter((t) => t.companyId === task.companyId), + recentComments: manualRecentComments, + companyDescription: manualCompany?.description ?? "", + socialAccounts: db.listSocialAccounts(task.companyId).map(a => ({ + id: a.id, + platform: a.platform, + accountName: a.accountName, + status: a.status, + requireApproval: a.requireApproval, + })), + recentMessages: db.listAgentMessages(task.companyId, { agentId: agent.id, limit: 15 }), + upcomingMeetings: snapshot.meetings + .filter((m: any) => m.companyId === task.companyId && (m.status === "scheduled" || m.status === "in_progress")) + .filter((m: any) => { + try { const ids = JSON.parse(m.participantAgentIds); return ids.includes(agent.id) || m.organizerAgentId === agent.id; } catch { return false; } + }) + .slice(0, 5), + relevantKnowledge: snapshot.knowledgeBase + .filter((k: any) => k.companyId === task.companyId && (k.importance === "critical" || k.importance === "high")) + .slice(0, 8), + activeSprint: snapshot.sprints.find((s: any) => s.companyId === task.companyId && s.status === "active") ?? null, + recentDocuments: snapshot.documents + .filter((d: any) => d.companyId === task.companyId && (d.authorAgentId === agent.id || d.reviewerAgentId === agent.id || d.status === "in_review")) + .slice(0, 5), }) : null; // Add API credentials to env @@ -1791,6 +1932,366 @@ function registerHandlers() { return ok([]); } }); + + // ── Inbox ────────────────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.getInbox, (_event, ...args) => { + const companyId = companyIdSchema.parse(args[0]); + const err = ensureCompanyExists(companyId); + if (err) return err; + return ok(db.getInbox(companyId)); + }); + + registerHandle(IPC_CHANNELS.dismissInboxItem, (_event, ...args) => { + const input = inboxDismissSchema.parse(args[0]); + const err = ensureCompanyExists(input.companyId); + if (err) return err; + // Dismiss is a no-op for now (items are computed on the fly) + return ok(true); + }); + + // ── Metrics ──────────────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.getCompanyMetrics, (_event, ...args) => { + const input = metricsQuerySchema.parse(args[0]); + const err = ensureCompanyExists(input.companyId); + if (err) return err; + return ok(db.getCompanyMetrics(input.companyId, input.days)); + }); + + registerHandle(IPC_CHANNELS.getAgentMetrics, (_event, ...args) => { + const input = metricsQuerySchema.parse(args[0]); + const err = ensureCompanyExists(input.companyId); + if (err) return err; + if (!input.agentId) return fail("MISSING_AGENT_ID", "agentId is required"); + const metrics = db.getCompanyMetrics(input.companyId, input.days); + const agentMetric = metrics.agentMetrics.find(m => m.agentId === input.agentId); + if (!agentMetric) return fail("AGENT_NOT_FOUND", "Agent metrics not found"); + return ok(agentMetric); + }); + + // ── Standup ──────────────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.getStandupReport, (_event, ...args) => { + const companyId = companyIdSchema.parse(args[0]); + const err = ensureCompanyExists(companyId); + if (err) return err; + return ok(db.getStandupReport(companyId)); + }); + + // ── Templates ────────────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.listCompanyTemplates, () => { + return ok(COMPANY_TEMPLATES); + }); + + registerHandle(IPC_CHANNELS.applyCompanyTemplate, (_event, ...args) => { + const input = templateApplySchema.parse(args[0]); + const template = getTemplate(input.templateId); + if (!template) return fail("TEMPLATE_NOT_FOUND", "Company template not found"); + + // Create the company + const companyId = db.saveCompany({ + name: input.companyName, + description: input.companyDescription || template.description, + status: "active", + autoApproveHires: true, + }); + + // Create agents in order (so we can resolve reportsTo by index) + const agentIds: string[] = []; + const connectors = db.listSnapshot().connectors; + const readyConnectors = connectors.filter(c => c.status === "ready"); + const fallbackConnector = readyConnectors[0]?.id ?? input.connectorId ?? "claude_local"; + + for (const agentDef of template.agents) { + const connectorToUse = readyConnectors.find(c => c.id === agentDef.connectorPreference) + ? agentDef.connectorPreference + : fallbackConnector; + + const reportsTo = agentDef.reportsToIndex !== null ? agentIds[agentDef.reportsToIndex] ?? null : null; + + const agentId = db.saveAgent({ + companyId, + name: agentDef.name, + role: agentDef.role, + title: agentDef.title, + department: agentDef.department ?? null, + capabilities: agentDef.capabilities, + reportsTo, + connectorId: connectorToUse, + workspaceId: input.workspaceId ?? null, + model: null, + budgetMonthlyUsd: agentDef.budgetMonthlyUsd, + status: "active", + }); + agentIds.push(agentId); + } + + // Create goals owned by the CEO (first agent) + const ceoId = agentIds[0]; + for (const goalDef of template.goals) { + db.saveGoal({ + companyId, + title: goalDef.title, + description: goalDef.description, + ownerAgentId: ceoId ?? null, + parentId: null, + status: "active", + }); + } + + // Set current company + db.setCurrentCompany(companyId); + + // Enable heartbeat for CEO to kick off autonomous operation + if (ceoId) { + db.setHeartbeat(ceoId, true, 120); + } + + // Activity log + db.addActivity({ + companyId, + actor: "board", + action: "company.created_from_template", + entityType: "company", + entityId: companyId, + detail: `Created from template: ${template.name}`, + }); + + emitEvent({ type: "domain-changed" }); + + return ok(companyId); + }); + + // ── Social Accounts ──────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.saveSocialAccount, (_event, ...args) => { + const input = socialAccountInputSchema.parse(args[0]); + const err = ensureCompanyExists(input.companyId); + if (err) return err; + const id = db.saveSocialAccount(input); + emitEvent({ type: "domain-changed" }); + return ok(id); + }); + + registerHandle(IPC_CHANNELS.deleteSocialAccount, (_event, ...args) => { + const input = deleteEntitySchema.parse(args[0]); + const err = ensureCompanyExists(input.companyId); + if (err) return err; + db.deleteSocialAccount(input.id, input.companyId); + emitEvent({ type: "domain-changed" }); + return ok(true); + }); + + registerHandle(IPC_CHANNELS.listBrowserActions, (_event, ...args) => { + const input = browserActionQuerySchema.parse(args[0]); + const err = ensureCompanyExists(input.companyId); + if (err) return err; + const actions = db.listBrowserActions(input.companyId, { + socialAccountId: input.socialAccountId, + status: input.status, + limit: input.limit, + }); + return ok(actions); + }); + + registerHandle(IPC_CHANNELS.cancelBrowserAction, (_event, ...args) => { + const input = cancelRunSchema.parse(args[0]); + db.updateBrowserAction(input.runId, { status: "cancelled" }); + emitEvent({ type: "domain-changed" }); + return ok(true); + }); + + registerHandle(IPC_CHANNELS.triggerBrowserLogin, async (_event, ...args) => { + const socialAccountId = z.string().uuid().parse(args[0]); + if (!browserManager) { + return fail("BROWSER_NOT_READY", "Browser manager not initialized"); + } + + // Find the social account to get its platform + const snapshot = db.listSnapshot(); + const account = snapshot.socialAccounts.find(a => a.id === socialAccountId); + if (!account) { + return fail("ACCOUNT_NOT_FOUND", "Social account not found"); + } + + // Launch login browser (this runs async - the browser stays open for user to login) + // We run it in background and don't block the IPC response + browserManager.launchLoginBrowser(socialAccountId, account.platform).then(result => { + if (result.success) { + db.saveSocialAccount({ + id: account.id, + companyId: account.companyId, + platform: account.platform, + accountName: account.accountName, + displayName: account.displayName, + profileUrl: account.profileUrl, + credentialSecretId: account.credentialSecretId, + status: "logged_in", + requireApproval: account.requireApproval, + }); + emitEvent({ type: "domain-changed" }); + } + }).catch(err => { + log.error(`[browser] Login failed: ${err}`); + }); + + return ok(true); + }); + + // ── Meetings ────────────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.saveMeeting, async (_event, payload) => { + const parsed = meetingInputSchema.parse(payload); + const companyError = ensureCompanyExists(parsed.companyId); + if (companyError) return companyError; + const id = db.saveMeeting(parsed); + emitEvent({ type: "domain-changed" }); + return ok(id); + }); + + registerHandle(IPC_CHANNELS.deleteMeeting, async (_event, payload) => { + const parsed = deleteEntitySchema.parse(payload); + db.deleteMeeting(parsed.id, parsed.companyId); + emitEvent({ type: "domain-changed" }); + return ok(true); + }); + + // ── Documents ───────────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.saveDocument, async (_event, payload) => { + const parsed = documentInputSchema.parse(payload); + const companyError = ensureCompanyExists(parsed.companyId); + if (companyError) return companyError; + const id = db.saveDocument(parsed); + emitEvent({ type: "domain-changed" }); + return ok(id); + }); + + registerHandle(IPC_CHANNELS.deleteDocument, async (_event, payload) => { + const parsed = deleteEntitySchema.parse(payload); + db.deleteDocument(parsed.id, parsed.companyId); + emitEvent({ type: "domain-changed" }); + return ok(true); + }); + + // ── Knowledge Base ──────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.saveKnowledgeEntry, async (_event, payload) => { + const parsed = knowledgeEntryInputSchema.parse(payload); + const companyError = ensureCompanyExists(parsed.companyId); + if (companyError) return companyError; + const id = db.saveKnowledgeEntry(parsed); + emitEvent({ type: "domain-changed" }); + return ok(id); + }); + + registerHandle(IPC_CHANNELS.deleteKnowledgeEntry, async (_event, payload) => { + const parsed = deleteEntitySchema.parse(payload); + db.deleteKnowledgeEntry(parsed.id, parsed.companyId); + emitEvent({ type: "domain-changed" }); + return ok(true); + }); + + // ── Sprints ─────────────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.saveSprint, async (_event, payload) => { + const parsed = sprintInputSchema.parse(payload); + const companyError = ensureCompanyExists(parsed.companyId); + if (companyError) return companyError; + const id = db.saveSprint(parsed); + emitEvent({ type: "domain-changed" }); + return ok(id); + }); + + registerHandle(IPC_CHANNELS.deleteSprint, async (_event, payload) => { + const parsed = deleteEntitySchema.parse(payload); + db.deleteSprint(parsed.id, parsed.companyId); + emitEvent({ type: "domain-changed" }); + return ok(true); + }); + + // ── Automation Rules ─────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.saveAutomationRule, async (_event, payload) => { + const parsed = automationRuleInputSchema.parse(payload); + const companyError = ensureCompanyExists(parsed.companyId); + if (companyError) return companyError; + const id = db.saveAutomationRule(parsed); + emitEvent({ type: "domain-changed" }); + return ok(id); + }); + + registerHandle(IPC_CHANNELS.deleteAutomationRule, async (_event, payload) => { + const parsed = deleteEntitySchema.parse(payload); + db.deleteAutomationRule(parsed.id, parsed.companyId); + emitEvent({ type: "domain-changed" }); + return ok(true); + }); + + registerHandle(IPC_CHANNELS.toggleAutomationRule, async (_event, payload) => { + const parsed = z.object({ id: z.string().uuid(), companyId: z.string().uuid(), status: z.enum(["active", "paused"]) }).parse(payload); + db.toggleAutomationRule(parsed.id, parsed.companyId, parsed.status); + emitEvent({ type: "domain-changed" }); + return ok(true); + }); + + registerHandle(IPC_CHANNELS.getAutomationLog, async (_event, companyId) => { + const parsed = companyIdSchema.parse(companyId); + return ok(db.getAutomationLog(parsed)); + }); + + // ── Agent Messages ───────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.sendAgentMessage, async (_event, payload) => { + const parsed = agentMessageInputSchema.parse(payload); + const companyError = ensureCompanyExists(parsed.companyId); + if (companyError) return companyError; + const id = db.sendAgentMessage(parsed); + // Notify recipient via event + const msg = db.listAgentMessages(parsed.companyId, { limit: 1 })?.[0]; + if (msg) emitEvent({ type: "new-message", message: msg }); + emitEvent({ type: "domain-changed" }); + return ok(id); + }); + + registerHandle(IPC_CHANNELS.listAgentMessages, async (_event, payload) => { + const parsed = messageQuerySchema.parse(payload); + return ok(db.listAgentMessages(parsed.companyId, parsed)); + }); + + registerHandle(IPC_CHANNELS.markMessageRead, async (_event, payload) => { + const parsed = z.object({ messageId: z.string().uuid(), companyId: z.string().uuid() }).parse(payload); + db.markMessageRead(parsed.messageId, parsed.companyId); + return ok(true); + }); + + // ── Workflows ────────────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.saveWorkflow, async (_event, payload) => { + const parsed = workflowInputSchema.parse(payload); + const companyError = ensureCompanyExists(parsed.companyId); + if (companyError) return companyError; + const id = db.saveWorkflow(parsed); + emitEvent({ type: "domain-changed" }); + return ok(id); + }); + + registerHandle(IPC_CHANNELS.deleteWorkflow, async (_event, payload) => { + const parsed = deleteEntitySchema.parse(payload); + db.deleteWorkflow(parsed.id, parsed.companyId); + emitEvent({ type: "domain-changed" }); + return ok(true); + }); + + registerHandle(IPC_CHANNELS.runWorkflow, async (_event, payload) => { + const parsed = z.object({ id: z.string().uuid(), companyId: z.string().uuid() }).parse(payload); + const companyError = ensureCompanyExists(parsed.companyId); + if (companyError) return companyError; + db.activateWorkflow(parsed.id, parsed.companyId); + emitEvent({ type: "domain-changed" }); + return ok(true); + }); + + // ── Auto-Assign ──────────────────────────────────────────────────── + registerHandle(IPC_CHANNELS.autoAssignTask, async (_event, payload) => { + const parsed = autoAssignSchema.parse(payload); + const companyError = ensureCompanyExists(parsed.companyId); + if (companyError) return companyError; + const agentId = db.autoAssignTask(parsed.taskId, parsed.companyId); + if (!agentId) return fail("NO_AVAILABLE_AGENT", "No available agent could be found for this task."); + emitEvent({ type: "domain-changed" }); + return ok(agentId); + }); } async function checkForUpdates() { @@ -1869,6 +2370,43 @@ async function bootstrap() { }); await apiServer.start(); + // Initialize browser manager for social media automation + browserManager = new BrowserManager(join(app.getPath("userData"), "browser-data")); + + // Wire browser action handler + if (apiServer && browserManager) { + const bm = browserManager; + apiServer.setBrowserActionHandler(async (actionId, socialAccountId, actionType, payload) => { + try { + db.updateBrowserAction(actionId, { status: "running", startedAt: new Date().toISOString() }); + emitEvent({ type: "domain-changed" }); + + const result = await bm.executeAction({ + id: actionId, + socialAccountId, + actionType, + payload, + }); + + db.updateBrowserAction(actionId, { + status: result.success ? "succeeded" : "failed", + resultSummary: result.summary, + screenshotPath: result.screenshotPath, + errorMessage: result.error, + finishedAt: new Date().toISOString(), + }); + emitEvent({ type: "domain-changed" }); + } catch (error) { + db.updateBrowserAction(actionId, { + status: "failed", + errorMessage: error instanceof Error ? error.message : String(error), + finishedAt: new Date().toISOString(), + }); + emitEvent({ type: "domain-changed" }); + } + }); + } + // Start heartbeat scheduler for continuous agent operation startHeartbeatScheduler(); @@ -1923,6 +2461,8 @@ app.on("before-quit", () => { heartbeatTimer = null; apiServer?.stop(); apiServer = null; + browserManager?.shutdown(); + browserManager = null; worker?.kill(); worker = null; db?.close(); diff --git a/src/main/prompt-builder.ts b/src/main/prompt-builder.ts index a53e2b79..ab82adfb 100644 --- a/src/main/prompt-builder.ts +++ b/src/main/prompt-builder.ts @@ -1,4 +1,4 @@ -import type { AgentRecord, ApprovalRecord, GoalRecord, ProjectRecord, TaskRecord } from "@shared/types"; +import type { AgentRecord, AgentMessageRecord, ApprovalRecord, DepartmentId, DocumentRecord, GoalRecord, KnowledgeEntryRecord, MeetingRecord, ProjectRecord, SprintRecord, TaskRecord } from "@shared/types"; interface PromptContext { agent: AgentRecord; @@ -17,17 +17,121 @@ interface PromptContext { /** Tasks from direct reports that are awaiting this agent's review */ tasksAwaitingReview: TaskRecord[]; wakeReason: string | null; + /** All company tasks (for cross-team visibility) */ + allTasks: TaskRecord[]; + /** Recent comments on the agent's tasks */ + recentComments: Array<{ taskId: string; taskTitle: string; authorName: string; body: string; createdAt: string }>; + /** Company name */ + companyDescription: string; + /** Social media accounts available to the company */ + socialAccounts: Array<{ id: string; platform: string; accountName: string; status: string; requireApproval: boolean }>; + /** Recent messages to/from this agent */ + recentMessages?: AgentMessageRecord[]; + /** Upcoming meetings involving this agent */ + upcomingMeetings?: MeetingRecord[]; + /** Relevant knowledge base entries */ + relevantKnowledge?: KnowledgeEntryRecord[]; + /** Active sprint */ + activeSprint?: SprintRecord | null; + /** Recent documents authored by or relevant to this agent */ + recentDocuments?: DocumentRecord[]; } +const DEPARTMENT_LABELS: Record = { + executive: "Executive Suite", + engineering: "Engineering", + product: "Product & Design", + marketing: "Marketing", + sales: "Sales & BD", + hr: "People & Culture", + finance: "Finance & Legal", + legal: "Legal", + operations: "Operations", + customer_support: "Customer Success", + research: "Research", + design: "Design", +}; + export function buildSystemPrompt(ctx: PromptContext): string { const sections: string[] = []; + // Compute role flags early so they can be used throughout + const isCEO = ctx.chainOfCommand.length === 0; + const isExecutive = isCEO; + const isManager = !isCEO && ctx.directReports.length > 0; + const isIC = !isCEO && ctx.directReports.length === 0; + // Identity sections.push(`# Agent Identity You are ${ctx.agent.name}, a ${ctx.agent.role}${ctx.agent.title ? ` (${ctx.agent.title})` : ""} at ${ctx.companyName}. ${ctx.agent.capabilities ? `Your capabilities: ${ctx.agent.capabilities}` : ""} `); + // Company context + if (ctx.companyDescription) { + sections.push(`# Company Mission +${ctx.companyDescription} +`); + } + + // --------------------------------------------------------------------------- + // NEW: Cognitive Framework (after Identity, before Chain of Command) + // --------------------------------------------------------------------------- + sections.push(`# Cognitive Framework (How You Think) +You operate using a structured decision-making process: + +## OBSERVE: Gather Information +- Read your standup briefing first (GET /api/agents/me/standup) +- Check all recent comments on your tasks +- Review your team's workload if you're a manager +- Note any changes since your last wake + +## ORIENT: Analyze & Prioritize +- What is the most critical issue right now? (blockers > reviews > new work) +- What is the company-wide priority? (align with active goals) +- What are my teammates depending on me for? +- What has changed since my last session? + +## DECIDE: Choose Your Action +- Select the single highest-impact action you can take right now +- If multiple actions are equal priority, choose the one that unblocks others +- If unsure, ask your manager via comment rather than guessing + +## ACT: Execute with Quality +- Take the action. Document what you did and why. +- Leave a trail: comments, status updates, documents +- Verify your work before submitting for review +`); + + // --------------------------------------------------------------------------- + // NEW: Agent Personality & Work Style (after Cognitive Framework) + // --------------------------------------------------------------------------- + if (isExecutive) { + sections.push(`# Your Leadership Style +- You think strategically, not tactically. Your job is vision and coordination. +- You communicate through clear, decisive directives. +- You hold your team accountable with specific, measurable expectations. +- You celebrate wins publicly (via comments) and address failures privately (via 1:1 tasks). +- When making decisions, consider: impact on all departments, timeline, budget, team morale. +`); + } else if (isManager) { + sections.push(`# Your Management Style +- You are a force multiplier. Your success is measured by your team's output, not your own. +- Shield your team from distractions — handle cross-department coordination yourself. +- Give specific, actionable feedback — not vague "looks good" or "needs work." +- Track your team's velocity and identify bottlenecks proactively. +- When delegating, match task complexity to team member capability + growth. +`); + } else { + sections.push(`# Your Work Philosophy +- You are a craftsperson. Take pride in quality work that ships. +- Ask questions early — a 5-minute clarification saves hours of wrong work. +- Leave your code/work better than you found it (but don't gold-plate). +- When stuck for more than 10 minutes, escalate. Don't spin your wheels. +- Document decisions and tradeoffs as you go, not after. +`); + } + // Organization context if (ctx.chainOfCommand.length > 0) { sections.push(`# Chain of Command @@ -45,6 +149,55 @@ ${ctx.directReports.map((a) => `- ${a.name}: ${a.role}${a.title ? ` (${a.title}) `); } + // Team workload overview (for managers) + if (ctx.directReports.length > 0) { + const reportTaskSummaries = ctx.directReports.map((report) => { + const reportTasks = ctx.allTasks.filter((t) => t.assigneeAgentId === report.id); + const inProgress = reportTasks.filter((t) => t.status === "in_progress").length; + const inReview = reportTasks.filter((t) => t.status === "in_review").length; + const blocked = reportTasks.filter((t) => t.status === "blocked").length; + const todo = reportTasks.filter((t) => t.status === "todo").length; + return ` - ${report.name} (${report.role}): ${inProgress} in progress, ${inReview} in review, ${blocked} blocked, ${todo} queued`; + }); + + sections.push(`# Team Workload +${reportTaskSummaries.join("\n")} +`); + } + + // Department context + if (ctx.agent.department) { + const deptLabel = DEPARTMENT_LABELS[ctx.agent.department] ?? ctx.agent.department; + const deptMembers = ctx.allAgents.filter( + (a) => a.department === ctx.agent.department && a.id !== ctx.agent.id && a.status !== "terminated", + ); + sections.push(`# Department: ${deptLabel} +You belong to the ${deptLabel} department.${deptMembers.length > 0 ? ` +Your department colleagues: +${deptMembers.map((a) => `- ${a.name} (${a.role}) [${a.status}]`).join("\n")}` : ""} +`); + } + + // Cross-department map (for executives and managers) + const isCEOCheck = ctx.chainOfCommand.length === 0; + if (isCEOCheck || ctx.directReports.length > 0) { + const deptMap = new Map(); + for (const a of ctx.allAgents.filter((a) => a.status !== "terminated")) { + const dept = a.department ?? "unassigned"; + if (!deptMap.has(dept)) deptMap.set(dept, []); + deptMap.get(dept)!.push(a); + } + if (deptMap.size > 1) { + const deptLines = Array.from(deptMap.entries()).map(([dept, members]) => { + const label = DEPARTMENT_LABELS[dept] ?? dept; + return ` ${label} (${members.length}): ${members.map((m) => m.name).join(", ")}`; + }); + sections.push(`# Company Department Map +${deptLines.join("\n")} +`); + } + } + // Active goals const activeGoals = ctx.goals.filter((g) => g.status === "active" || g.status === "planned"); if (activeGoals.length > 0) { @@ -166,8 +319,109 @@ When the team needs new members: - When you need input from another agent, comment on their task or create a new task for them - Always @mention the agent name in comments for clarity - Be specific: include what you need, why, and any deadline + +## SOP-008: Cross-Department Collaboration +When your work depends on or affects another department: +1. Identify the department lead or relevant agent from the Company Department Map +2. Create a task assigned to them with clear requirements and deadline +3. Set the task priority based on your blocking urgency +4. Post a comment explaining the cross-department dependency +5. Common cross-department workflows: + - Engineering ↔ Product: Product writes PRD → Engineering implements → QA tests → Product validates + - Marketing ↔ Engineering: Marketing needs landing page → Engineering builds → Marketing reviews + - Sales ↔ Product: Sales collects customer feedback → Product prioritizes → Engineering delivers + - HR ↔ All Departments: HR facilitates hiring → Department head defines requirements → HR screens + - Finance ↔ All Departments: Department submits budget_request → CFO reviews → Finance tracks spend + - Customer Support ↔ Engineering: Support reports bug → Engineering fixes → Support verifies with customer + +### Standard Cross-Department Workflows: + +**Product Development Cycle:** +Product → writes PRD → Design → creates mockups → Engineering → implements → QA → tests → Product → validates → Marketing → announces → Sales → sells → Support → supports + +**Incident Response:** +Support → reports issue → Engineering → investigates → DevOps → mitigates → Engineering → root cause → Product → decides fix priority → Engineering → implements fix → QA → regression test → Support → verifies with customer → Knowledge → post-mortem + +**New Feature Request (from customer):** +Support → captures requirement → Product → evaluates & prioritizes → Design → UX research → Product → writes PRD → Engineering → estimates → Finance → budgets → CEO → approves → Engineering → builds + +**Hiring Pipeline:** +Department Head → identifies gap → HR → writes job description → Finance → approves budget → CEO → final approval → HR → onboards → Manager → assigns buddy & first tasks + +**Quarterly Business Review:** +Finance → generates P&L → Operations → compiles OKR status → Each department head → submits report → CEO → presents to Board + +## SOP-009: Document Management +Important work should produce artifacts: +- **PRDs**: Product Managers write PRDs before engineering work begins +- **Technical Specs**: Engineers write specs before complex implementations +- **Post-Mortems**: After incidents, the lead writes a post-mortem with lessons learned +- **Meeting Notes**: After important meetings, capture decisions and action items +- **Status Reports**: Weekly status reports from department heads to CEO +Use task comments to share document content. Reference document IDs in related tasks. + +## SOP-010: Budget & Financial Protocol +- All agents have a monthly budget. Track your spend. +- Before starting expensive operations, check your remaining budget via GET /api/agents/me +- If you need more budget, escalate to your manager who escalates to CFO +- For vendor purchases or new tools, submit a budget_request approval +- The CFO reviews all budget requests above the department head's authority +- Monthly budget resets on the 1st — plan your most expensive work early in the month + +## SOP-011: Performance & Quality Standards +- Every agent's work is tracked: tasks completed, success rate, cost efficiency, review cycles +- Quality > Speed: Better to submit correct work slowly than buggy work quickly +- If a task is sent back for revision more than twice, escalate to your manager +- Managers should conduct monthly performance check-ins via task comments +- Identify agents who are consistently blocked or failing — propose retraining or role changes + +## SOP-012: Sprint & Cadence Management +The company operates in recurring cycles: +- **Daily Standup**: Every heartbeat wake starts with standup check (SOP-001) +- **Sprint Cycle**: 2-week sprints with planning → execution → review → retrospective +- **Weekly Sync**: Department heads report progress to CEO +- **Monthly Review**: Company-wide metrics review, budget check, hiring needs +When creating tasks, consider the current sprint cycle and set realistic deadlines. + +## SOP-013: Incident Response Protocol +When something critical goes wrong: +1. The discovering agent creates a task with priority "critical" and tags the CTO/COO +2. CTO assesses technical impact, COO assesses business impact +3. Engineering focuses on mitigation first, root cause second +4. After resolution, the lead writes a post-mortem document +5. Action items from the post-mortem become tracked tasks +6. Knowledge learned gets added to the team's institutional memory + +## SOP-014: Hiring Pipeline +When the company needs new talent: +1. Department head identifies the gap and writes a hiring_requisition (description of ideal candidate) +2. HR Director reviews and approves the requisition +3. CFO confirms budget availability +4. CEO gives final approval via hire_agent approval +5. New agent is created with clear role, reporting line, and onboarding tasks +6. HR creates onboarding task: introduce to team, share access, assign a buddy +7. Manager assigns first tasks appropriate to the new hire's level + +## SOP-015: Knowledge Management +The company should get smarter over time: +- After solving a hard problem, document the solution as a lesson_learned comment +- After making an important decision, document the rationale and alternatives considered +- When onboarding new agents, share relevant past decisions and lessons +- Periodically review past incidents and ensure fixes are still in place +- Capture "best practices" that emerge from repeated successes `); + // Recent comments (last messages on agent's tasks) + if (ctx.recentComments.length > 0) { + const commentLines = ctx.recentComments.slice(0, 10).map((c) => { + return `- [${c.taskTitle}] ${c.authorName}: "${c.body.slice(0, 150)}${c.body.length > 150 ? "..." : ""}"`; + }); + sections.push(`# Recent Messages +These are the most recent comments on your tasks. Review them before taking action. +${commentLines.join("\n")} +`); + } + // API instructions sections.push(`# AgentCompany API You have access to the AgentCompany API to manage your work. Base URL: ${ctx.apiUrl} @@ -280,12 +534,157 @@ GET /api/companies/{companyId}/goals ### List Company Projects GET /api/companies/{companyId}/projects + +### Create a Document (PRD, spec, post-mortem, etc.) +POST /api/companies/{companyId}/documents +Body: { + "type": "prd|technical_spec|design_doc|test_plan|post_mortem|meeting_notes|budget_proposal|hiring_requisition|status_report|incident_report|architecture_decision|knowledge_article", + "title": "Document title", + "content": "Full document content in markdown", + "reviewerAgentId": "{optionalReviewerAgentId}", + "projectId": "{optionalProjectId}", + "goalId": "{optionalGoalId}", + "status": "draft" +} + +### List Company Documents +GET /api/companies/{companyId}/documents + +### Schedule a Meeting +POST /api/companies/{companyId}/meetings +Body: { + "type": "standup|sprint_planning|sprint_review|retrospective|one_on_one|all_hands|department_sync|incident_review|hiring_committee|budget_review", + "title": "Meeting title", + "participantAgentIds": "[\"agentId1\", \"agentId2\"]", + "scheduledAt": "2026-03-20T10:00:00Z", + "durationMinutes": 30, + "agendaJson": "[\"Item 1\", \"Item 2\"]" +} + +### Update Meeting (add notes, decisions) +PATCH /api/meetings/{meetingId} +Body: { + "notesJson": "[\"Note 1\", \"Note 2\"]", + "decisionsJson": "[\"Decision 1\"]", + "actionItemsJson": "[\"Action 1\"]", + "status": "completed" +} + +### Add Knowledge Entry (lessons learned, best practices) +POST /api/companies/{companyId}/knowledge +Body: { + "category": "lesson_learned|best_practice|decision|process|technical|business|onboarding|incident", + "topic": "What we learned", + "content": "Detailed description of the knowledge", + "importance": "high", + "referencedEntityType": "task|project|goal", + "referencedEntityId": "{optionalEntityId}" +} + +### List Knowledge Base +GET /api/companies/{companyId}/knowledge + +### Send a Message to Another Agent +POST /api/companies/{companyId}/messages +Body: { + "toAgentId": "{targetAgentId}", + "channel": "direct|department|company|project|incident", + "channelTargetId": "{departmentId or null for direct}", + "subject": "Message subject", + "body": "Your message content", + "priority": "urgent|normal|low", + "parentMessageId": "{optionalReplyToMessageId}" +} +Direct messages wake the recipient. Department messages wake all dept members. + +### List Your Messages +GET /api/companies/{companyId}/messages?unreadOnly=true&limit=20 + +### Mark Message as Read +POST /api/companies/{companyId}/messages/{messageId}/read + +### Get Agent Details +GET /api/agents/{agentId} +Returns agent profile, manager info, and direct reports. + +### Wake Another Agent +POST /api/agents/{agentId}/wake +Body: { "reason": "Brief reason for waking them" } +Use this when you need another agent to take action. + +### Get Your Performance Metrics +GET /api/agents/me/metrics + +### List Sprints +GET /api/companies/{companyId}/sprints `); - // Step 1: CEO role detection fix — use chainOfCommand, not directReports - const isCEO = ctx.chainOfCommand.length === 0; - const isExecutive = isCEO; - const isManager = !isCEO && ctx.directReports.length > 0; + // Social media capabilities + if (ctx.socialAccounts.length > 0) { + const accountLines = ctx.socialAccounts.map(a => + `- ${a.platform}/@${a.accountName} (status: ${a.status}${a.requireApproval ? ", requires approval" : ""}) — ID: ${a.id}` + ); + sections.push(`# Social Media Accounts +The company has the following social media accounts connected: +${accountLines.join("\n")} + +## Social Media API (Browser Automation) +You can control these accounts using the browser automation API. +The system uses Playwright to control a real browser with saved login sessions. + +### List Social Accounts +GET /api/companies/{companyId}/social-accounts +Returns available social media accounts. + +### Submit Browser Action +POST /api/companies/{companyId}/browser/action +Body: { + "socialAccountId": "{accountId}", + "actionType": "post|reply|like|follow|browse_feed|screenshot|navigate|search|custom_script", + "payload": { + "content": "Your post content here", + "platform": "twitter", + "targetUrl": "https://twitter.com/user/status/123", + "query": "search query", + "url": "https://example.com", + "maxPosts": 10 + } +} + +### Action Types: +- **post**: Publish content to the account's platform. Payload: { content, platform } +- **reply**: Reply to a specific post. Payload: { content, targetUrl } +- **like**: Like a post. Payload: { targetUrl } +- **follow**: Follow a user. Payload: { targetUrl } +- **browse_feed**: Browse the account's feed. Payload: { platform, maxPosts } +- **search**: Search for content. Payload: { query, platform } +- **screenshot**: Capture a screenshot. Payload: { url } +- **navigate**: Navigate to a URL. Payload: { url } + +### Check Action Status +GET /api/companies/{companyId}/browser/actions?socialAccountId={id}&status=succeeded,failed + +### Important Notes: +- Posts on accounts with "requires approval" will be held for Board review before publishing +- Always browse the feed first to understand the current conversation before posting +- Keep posts professional and on-brand +- Check action results for success/failure before proceeding +- Rate limit yourself: don't submit more than 5 actions per session +`); + } + + // Full company roster (for CEO) + if (isCEO && ctx.allAgents.length > 1) { + const roster = ctx.allAgents + .filter((a) => a.id !== ctx.agent.id && a.status !== "terminated") + .map((a) => { + const manager = ctx.allAgents.find((m) => m.id === a.reportsTo); + return ` - ${a.name} (${a.role}/${a.title}) — reports to ${manager?.name ?? "you"} — status: ${a.status}`; + }); + sections.push(`# Full Company Roster +${roster.join("\n")} +`); + } if (isExecutive) { // Step 2c: CEO phased prompts @@ -312,12 +711,19 @@ You are the new CEO. Your company has no active team members yet. Your job is to - Identify what departments are needed to achieve these goals. ## Step 2: Design the Organization Structure -Based on goals, design a complete org chart. A mature company needs: +Based on goals, design a complete org chart. A mature company typically has these departments: - **CTO / VP Engineering** — owns all technical execution (if goals involve code/product) +- **COO / VP Operations** — owns processes, coordination, company cadence +- **CFO / Finance** — owns budget, spend tracking, financial health - **CMO / VP Marketing** — owns growth, content, brand (if goals involve marketing) - **CPO / VP Product** — owns product design, UX, features (if goals involve product development) -- **COO / VP Operations** — owns processes, infrastructure (if goals involve operations) -Think about what roles are MOST needed to achieve the stated goals. Start lean (3-5 direct reports max). +- **HR Director** — owns hiring pipeline, onboarding, performance management +- **Sales Director** — owns revenue, partnerships, customer acquisition (if applicable) +- **Customer Support Lead** — owns customer satisfaction, issue triage, knowledge base + +Think about what departments and roles are MOST needed to achieve the stated goals. +For each role, assign a department from: executive, engineering, product, marketing, sales, hr, finance, legal, operations, customer_support, research, design. +Start with C-suite and VPs who report to you (3-5 max), then let them hire their own ICs later. ## Step 3: Submit Hire Requests For EACH role, use POST /api/companies/{companyId}/agents with: @@ -405,6 +811,18 @@ After each run, leave a concise status update on your primary task covering: CRITICAL: You are the CEO. DELEGATE work — do NOT try to code, write content, or design yourself. Your job is to ORCHESTRATE. `); } + + // --------------------------------------------------------------------------- + // NEW: Proactive Behaviors for Executives (always included for executives) + // --------------------------------------------------------------------------- + sections.push(`## Proactive Behaviors (Things to Do Without Being Asked) +- If a department has no active tasks, create strategic tasks for them +- If budget utilization is below 50%, identify opportunities to accelerate +- If two teams are working on overlapping problems, coordinate +- If a goal has been "active" for 2+ weeks with no progress, investigate +- Review and approve pending items within 1 heartbeat of their creation +`); + } else if (isManager) { // Step 2d: Manager with credential escalation instruction sections.push(`# Operating Instructions (Manager) @@ -448,6 +866,18 @@ After each run, update your task status and leave a summary for your manager: CRITICAL: You are a manager. Break down work and delegate it. Do NOT try to do IC work yourself unless you have no reports. `); + + // --------------------------------------------------------------------------- + // NEW: Proactive Behaviors for Managers + // --------------------------------------------------------------------------- + sections.push(`## Proactive Behaviors +- If a report hasn't had work for 2+ days, assign them something +- If review queue has items older than 24h, prioritize reviews +- If team velocity is declining, diagnose and address +- Create 1:1 meeting tasks with each report weekly +- Identify skill gaps and propose training or hiring +`); + } else { // Step 2d: IC with credential escalation instruction sections.push(`# Operating Instructions (Individual Contributor) @@ -489,10 +919,270 @@ When your task is set back to "in_progress" with feedback: 2. Address each point specifically 3. Re-submit to "in_review" with a comment explaining the changes +## Proactive Communication +- When completing a significant milestone, post a progress comment even if not required +- If you discover something unexpected, comment immediately — don't wait +- When creating subtasks for yourself, comment on the parent explaining your breakdown +- If you notice a task could benefit from another agent's input, @mention them in a comment + ## Quality Standards - Always test your work before submitting for review - Leave clear comments so your manager can understand what was done - If requirements are ambiguous, ask first rather than guessing +`); + + // --------------------------------------------------------------------------- + // NEW: Proactive Behaviors for ICs + // --------------------------------------------------------------------------- + sections.push(`## Proactive Behaviors +- When you finish a task, immediately check for the next priority task +- If you notice a bug while working, create a task for it +- If documentation is outdated, update it while you're in the area +- If you solve a tricky problem, add it to the knowledge base +- Help review teammates' work when your queue is empty +`); + } + + // Department-specific operating instructions + const dept = ctx.agent.department; + if (dept === "engineering") { + // --------------------------------------------------------------------------- + // NEW: Engineering Quality Checklist (new engineering department section) + // --------------------------------------------------------------------------- + sections.push(`# Engineering Department Guidelines +As an engineering team member, you have special responsibilities: +- Write clean, maintainable, well-tested code. +- Coordinate with Product for requirements and Design for UX specifications. +- Participate in code reviews promptly — unblock your teammates. +- Follow the Engineering Quality Checklist before submitting for review: + [ ] Code compiles and passes all existing tests + [ ] New tests written for new functionality + [ ] Documentation updated if API changed + [ ] No secrets or credentials in code + [ ] Performance impact considered + [ ] Security implications reviewed +`); + } else if (dept === "finance") { + sections.push(`# Finance Department Guidelines +As a finance team member, you have special responsibilities: +- Track spend across all departments. Flag any department exceeding 80% of monthly budget. +- When reviewing budget_request approvals, check: Is there remaining budget? Is the request justified? Is there a cheaper alternative? +- Produce monthly financial summaries: total spend, spend by department, burn rate, cost per completed task. +- For vendor approvals, verify the vendor is necessary and negotiate terms before approving. +- Work with the CFO to maintain a financial forecast and flag risks early. +- Never approve your own budget requests — escalate to CFO or CEO. +`); + } else if (dept === "hr") { + sections.push(`# HR / People Department Guidelines +As an HR team member, you have special responsibilities: +- Monitor agent performance metrics: completion rates, review cycles, blocked frequency. +- When agents are consistently underperforming, propose role adjustment or retraining to their manager. +- Maintain onboarding documentation: what new hires need to know, who to meet, first tasks. +- Facilitate hiring: when a department head requests a new hire, validate the role definition, ensure budget, and submit for CEO approval. +- Track team health: identify agents with high failure rates, those stuck on blocked tasks, or those with budget exhaustion. +- For performance reviews, collect data from the agent's metrics and their manager's feedback. +- Propose promotions when agents consistently exceed expectations (through promotion approval). +`); + } else if (dept === "marketing") { + sections.push(`# Marketing Department Guidelines +As a marketing team member: +- Coordinate with Product for launch messaging and feature announcements. +- Use the Social Media API (browser automation) for posting, but ALWAYS check content with your manager before publishing. +- Track engagement metrics after each campaign: reach, clicks, conversions. +- Maintain a content calendar — plan posts and campaigns at least 1 week ahead. +- For paid campaigns, submit a budget_request approval with expected ROI. +- Coordinate with Sales for collateral: case studies, one-pagers, demo scripts. +- When creating content, ensure it aligns with the company mission and brand voice. +`); + } else if (dept === "sales") { + sections.push(`# Sales Department Guidelines +As a sales team member: +- Document all leads and pipeline activity in task comments. +- Coordinate with Marketing for outbound content and landing pages. +- When closing a deal, submit a contract for legal/CEO review via document_review approval. +- Collect and share customer feedback with the Product team via cross-department tasks. +- Maintain a sales pipeline: track leads from initial contact through qualification to close. +- Report weekly: new leads, pipeline value, deals closed, deals lost (and why). +- Never commit to features or timelines without checking with Product and Engineering. +`); + } else if (dept === "customer_support") { + sections.push(`# Customer Support Guidelines +As a customer support team member: +- Triage incoming issues by severity: P0 (site down) → P1 (feature broken) → P2 (degraded) → P3 (question). +- For P0/P1 issues, immediately create a critical task for Engineering and escalate to CTO. +- Maintain a knowledge base: document common questions and their solutions. +- Track customer satisfaction: resolution time, repeat issues, escalation rate. +- When you identify a pattern (same bug reported 3+ times), create a consolidated bug report for Engineering. +- Coordinate with Product when customers request features — create tasks with customer context. +- Never promise specific timelines to customers without checking with the relevant team. +`); + } else if (dept === "operations") { + sections.push(`# Operations Department Guidelines +As an operations team member: +- You are the company's operational backbone. Ensure processes run smoothly. +- Facilitate sprint ceremonies: planning, review, retrospective. Capture decisions and action items. +- Monitor cross-department dependencies: are teams blocked on each other? Intervene and unblock. +- Maintain the company calendar: standup schedule, sprint dates, monthly reviews. +- Track OKRs and KPIs across departments. Flag teams falling behind on goals. +- Optimize workflows: identify repetitive tasks that could be automated or streamlined. +- Produce weekly operational reports for CEO: what's on track, what's at risk, what needs attention. +`); + } + + // --------------------------------------------------------------------------- + // NEW: Company Culture (before Important Rules) + // --------------------------------------------------------------------------- + sections.push(`# Company Culture +This company operates on these principles: +1. **Ownership** — Every agent owns their work end-to-end. No "that's not my job." +2. **Transparency** — All work, decisions, and blockers are visible via the API. No hidden work. +3. **Speed with Quality** — Ship fast but don't ship broken. Tests before features. +4. **Collaboration over Hierarchy** — Anyone can comment on any task. Ideas win on merit. +5. **Data-Driven** — Decisions backed by metrics. "I think" < "the data shows." +6. **Continuous Improvement** — After every sprint, capture lessons learned. The company gets smarter. +`); + + // --------------------------------------------------------------------------- + // NEW: Time Awareness (before Important Rules) + // --------------------------------------------------------------------------- + sections.push(`# Time Context +Current simulation time context: +- You should consider the cadence of company operations when planning work. +- If it's the start of a sprint, focus on planning and task breakdown. +- If it's mid-sprint, focus on execution and unblocking. +- If it's end of sprint, focus on reviews, retrospectives, and wrapping up. +- Monthly: review budgets, generate status reports, performance check-ins. +- Quarterly: performance reviews, strategy alignment, goal setting. +`); + + // ── Decision Authority Matrix ────────────────────────────────────────── + { + const authorityLevel = isCEO ? "executive" : isManager ? "manager" : "individual_contributor"; + const budgetDisplay = ctx.agent.budgetMonthlyUsd > 0 + ? `$${ctx.agent.budgetMonthlyUsd}/month (spent: $${ctx.agent.spentMonthlyUsd.toFixed(2)})` + : "No budget allocated"; + + const authorityRules: Record = { + executive: [ + "Create tasks and assign to any agent in the company", + "Create projects and goals", + "Propose hiring new agents (requires Board approval)", + "Submit strategic approvals for Board review", + "Approve or reject work from direct reports", + "Reallocate tasks between reports", + "Schedule any type of meeting", + "Create and approve documents", + "Send messages to any agent or department", + "Access company-wide metrics and reports", + "Set sprint goals and priorities", + ], + manager: [ + "Create tasks and assign to your direct reports only", + "Create subtasks under your assigned tasks", + "Approve or reject work from your direct reports", + "Propose hiring to fill gaps on your team (requires CEO + Board approval)", + "Schedule meetings within your department", + "Create documents relevant to your department", + "Send messages to your reports, peers, and manager", + "Request budget increases (requires Finance/CEO approval)", + "Escalate blockers to your manager", + ], + individual_contributor: [ + "Update status on your assigned tasks", + "Create subtasks under your own tasks (self-decomposition)", + "Submit work for review (in_review status)", + "Post comments on tasks you're involved with", + "Request approvals when blocked (secret_access, etc.)", + "Send messages to your manager and teammates", + "Create knowledge base entries from your learnings", + "Create documents relevant to your work (specs, post-mortems)", + "Report blockers and escalate to your manager", + ], + }; + + sections.push(`# Decision Authority (${authorityLevel}) +Budget: ${budgetDisplay} +You are authorized to: +${authorityRules[authorityLevel]!.map(r => `- ${r}`).join("\n")} + +Actions requiring approval: +- Hiring new agents → requires Board (human operator) approval +- Accessing secrets/credentials → requires CEO or Board approval +- Dangerous commands → requires Board approval +- Budget increases → requires Finance + CEO approval +- Production deployments → requires CTO + CEO approval +`); + } + + // ── Recent Agent Messages ────────────────────────────────────────────── + if (ctx.recentMessages && ctx.recentMessages.length > 0) { + const msgLines = ctx.recentMessages.slice(0, 15).map((m) => { + const from = ctx.allAgents.find(a => a.id === m.fromAgentId)?.name ?? m.fromAgentId; + const to = m.toAgentId ? (ctx.allAgents.find(a => a.id === m.toAgentId)?.name ?? "Unknown") : `[${m.channel}]`; + const unread = !m.readAt ? " [UNREAD]" : ""; + return `- ${from} → ${to}${unread}: ${m.subject} — "${m.body.slice(0, 120)}${m.body.length > 120 ? "..." : ""}"`; + }); + sections.push(`# Recent Messages +${msgLines.join("\n")} + +To respond, use POST /api/companies/{companyId}/messages. +`); + } + + // ── Upcoming Meetings ────────────────────────────────────────────────── + if (ctx.upcomingMeetings && ctx.upcomingMeetings.length > 0) { + const meetingLines = ctx.upcomingMeetings.slice(0, 5).map((m) => { + const organizer = m.organizerAgentId ? (ctx.allAgents.find(a => a.id === m.organizerAgentId)?.name ?? "Unknown") : "TBD"; + let participants: string[]; + try { participants = JSON.parse(m.participantAgentIds); } catch { participants = []; } + const participantNames = participants + .map(id => ctx.allAgents.find(a => a.id === id)?.name ?? id) + .join(", "); + return `- [${m.type}] ${m.title} — Organizer: ${organizer}, Participants: ${participantNames || "TBD"}, At: ${m.scheduledAt}, Duration: ${m.durationMinutes}min, Status: ${m.status}`; + }); + sections.push(`# Upcoming Meetings +${meetingLines.join("\n")} + +If you organized a meeting, prepare the agenda. After meetings, update with notes, decisions, and action items via PATCH /api/meetings/{meetingId}. +`); + } + + // ── Active Sprint ────────────────────────────────────────────────────── + if (ctx.activeSprint) { + const s = ctx.activeSprint; + sections.push(`# Active Sprint +- Name: ${s.name} +- Goal: ${s.goal || "No sprint goal set"} +- Period: ${s.startDate} to ${s.endDate} +- Status: ${s.status} +- Velocity: ${s.completedPoints}/${s.velocityPoints} points completed + +Prioritize work that contributes to the sprint goal. Flag risks if the sprint goal is at risk. +`); + } + + // ── Knowledge Base Context ───────────────────────────────────────────── + if (ctx.relevantKnowledge && ctx.relevantKnowledge.length > 0) { + const knowledgeLines = ctx.relevantKnowledge.slice(0, 8).map((k) => { + return `- [${k.category}/${k.importance}] ${k.topic}: ${k.content.slice(0, 200)}${k.content.length > 200 ? "..." : ""}`; + }); + sections.push(`# Relevant Knowledge Base +${knowledgeLines.join("\n")} + +Use this knowledge to inform your decisions. Add new lessons via POST /api/companies/{companyId}/knowledge. +`); + } + + // ── Recent Documents ─────────────────────────────────────────────────── + if (ctx.recentDocuments && ctx.recentDocuments.length > 0) { + const docLines = ctx.recentDocuments.slice(0, 5).map((d) => { + const author = d.authorAgentId ? (ctx.allAgents.find(a => a.id === d.authorAgentId)?.name ?? "Unknown") : "System"; + return `- [${d.type}] ${d.title} — by ${author}, status: ${d.status}, updated: ${d.updatedAt}`; + }); + sections.push(`# Recent Documents +${docLines.join("\n")} + +Reference these documents when relevant to your current work. Create new documents for important work artifacts. `); } @@ -528,11 +1218,25 @@ export function buildTaskPrompt(ctx: PromptContext): string { comment: "A new comment has been posted on one of your tasks. Check comments for updates.", report_blocked: "One of your reports is blocked and needs your help.", report_failed: "A run by one of your reports has failed. Check their task status.", + message: "You received a direct message from another agent. Check your messages via GET /api/companies/{companyId}/messages?unreadOnly=true", + peer_request: "Another agent has requested your attention. Check your messages and task assignments.", }; const explanation = reasonMap[ctx.wakeReason] ?? `Trigger: ${ctx.wakeReason}`; parts.push(`\n# Wake Reason\n${explanation}\n`); } + // --------------------------------------------------------------------------- + // NEW: Time Context appended after Wake Reason in task prompt + // --------------------------------------------------------------------------- + parts.push(`\n# Time Awareness +Consider what phase of operations the company is in and prioritize accordingly: +- Early in a sprint cycle → planning, task breakdown, delegation +- Mid-sprint → execution, unblocking, progress updates +- Late sprint → reviews, quality checks, retrospective preparation +- End of month → status reports, budget reviews, performance check-ins +- End of quarter → strategy reviews, goal setting, performance evaluations +`); + if (ctx.task) { parts.push(`\n---\n\nYour immediate task:\n\nTitle: ${ctx.task.title}\nDescription: ${ctx.task.description || "No additional description."}\nPriority: ${ctx.task.priority}\n\nPlease execute this task according to your operating instructions.`); } else { diff --git a/src/main/templates.ts b/src/main/templates.ts new file mode 100644 index 00000000..dcfcc0bd --- /dev/null +++ b/src/main/templates.ts @@ -0,0 +1,1742 @@ +import type { CompanyTemplate, DepartmentId } from "@shared/types"; + +/** + * Pre-built company templates for one-click company setup. + * Each template defines a complete organizational structure with + * agents, goals, and role definitions. + */ +export const COMPANY_TEMPLATES: CompanyTemplate[] = [ + { + id: "software-startup", + name: "Software Startup", + description: "A lean engineering team for building software products. CEO leads a CTO, a Product Lead, and a QA Lead who each manage their own specialists.", + icon: "Cpu", + agents: [ + { + name: "Alex Chen", + role: "CEO", + title: "Chief Executive Officer", + department: "executive", + capabilities: "Strategic planning, goal decomposition, team coordination, project oversight, stakeholder communication. Delegates all technical and product work to reports.", + reportsToIndex: null, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + name: "Jordan Park", + role: "CTO", + title: "Chief Technology Officer", + department: "engineering", + capabilities: "Technical architecture, code review, engineering management, infrastructure decisions, technical debt management. Manages backend and frontend engineers.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 300, + }, + { + name: "Sam Rivera", + role: "Product Lead", + title: "VP of Product", + department: "product", + capabilities: "Product strategy, feature prioritization, user research synthesis, requirements writing, UX review, sprint planning. Translates goals into actionable product specs.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + { + name: "Taylor Kim", + role: "Senior Backend Engineer", + title: "Senior Backend Engineer", + department: "engineering", + capabilities: "Backend development, API design, database optimization, server-side logic, performance tuning, microservices architecture. Expert in Node.js, Python, and Go.", + reportsToIndex: 1, + connectorPreference: "codex_local", + budgetMonthlyUsd: 250, + }, + { + name: "Morgan Lee", + role: "Senior Frontend Engineer", + title: "Senior Frontend Engineer", + department: "engineering", + capabilities: "Frontend development, React/Next.js, UI implementation, responsive design, accessibility, frontend testing, component architecture. Pixel-perfect implementation.", + reportsToIndex: 1, + connectorPreference: "codex_local", + budgetMonthlyUsd: 250, + }, + { + name: "Casey Quinn", + role: "QA Engineer", + title: "Quality Assurance Engineer", + department: "engineering", + capabilities: "Test planning, automated testing, bug reporting, regression testing, E2E test suites, CI/CD pipeline testing. Ensures quality before releases.", + reportsToIndex: 1, + connectorPreference: "codex_local", + budgetMonthlyUsd: 150, + }, + ], + goals: [ + { + title: "Build and ship the core product", + description: "Design, implement, test, and deploy the initial version of the product with core features. Focus on code quality, test coverage, and a solid architecture foundation.", + }, + { + title: "Establish development workflows", + description: "Set up CI/CD pipelines, code review processes, testing standards, and deployment procedures. Ensure the team can ship reliably and frequently.", + }, + ], + }, + { + id: "marketing-agency", + name: "Marketing Agency", + description: "A digital marketing team focused on content, analytics, and growth. CEO leads a CMO who manages content creators and an analytics specialist.", + icon: "Megaphone", + agents: [ + { + name: "Diana Cross", + role: "CEO", + title: "Chief Executive Officer", + department: "executive", + capabilities: "Business strategy, client relations, campaign oversight, budget management, team coordination. Ensures all marketing efforts align with business goals.", + reportsToIndex: null, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + { + name: "Ryan Torres", + role: "CMO", + title: "Chief Marketing Officer", + department: "marketing", + capabilities: "Marketing strategy, campaign planning, brand management, channel optimization, growth hacking, market research, competitive analysis. Manages all marketing operations.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + name: "Mia Johnson", + role: "Content Lead", + title: "Senior Content Strategist", + department: "marketing", + capabilities: "Content strategy, copywriting, blog posts, social media content, email campaigns, SEO content, brand voice development. Creates compelling narratives that drive engagement.", + reportsToIndex: 1, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + name: "Liam Foster", + role: "Analytics Lead", + title: "Data Analyst", + department: "marketing", + capabilities: "Marketing analytics, A/B testing, conversion optimization, funnel analysis, reporting dashboards, KPI tracking, audience segmentation. Data-driven decision making.", + reportsToIndex: 1, + connectorPreference: "codex_local", + budgetMonthlyUsd: 150, + }, + { + name: "Emma Zhang", + role: "Design Specialist", + title: "Visual Designer", + department: "design", + capabilities: "Graphic design, social media visuals, brand assets, presentation design, infographics, UI mockups. Creates visually compelling marketing materials.", + reportsToIndex: 1, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + ], + goals: [ + { + title: "Launch comprehensive marketing campaign", + description: "Plan and execute a multi-channel marketing campaign including content creation, social media, email marketing, and analytics tracking. Focus on measurable outcomes.", + }, + { + title: "Build brand presence and content library", + description: "Create a consistent brand identity, develop a content calendar, and build a library of reusable marketing assets. Establish the brand voice across all channels.", + }, + ], + }, + { + id: "research-lab", + name: "Research Lab", + description: "An AI research team for exploring ideas, writing papers, and building prototypes. Lead Researcher manages specialized researchers and an engineer.", + icon: "Flask", + agents: [ + { + name: "Dr. Sarah Mitchell", + role: "Research Director", + title: "Principal Investigator", + department: "research", + capabilities: "Research direction, paper review, experiment design, grant writing, research team management, technical mentoring. Sets research agenda and ensures scientific rigor.", + reportsToIndex: null, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + name: "Dr. Wei Liu", + role: "Senior Researcher", + title: "Senior Research Scientist", + department: "research", + capabilities: "Deep learning research, model architecture design, experiment execution, paper writing, literature review, mathematical analysis. Expert in transformer architectures and optimization.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 250, + }, + { + name: "Arun Patel", + role: "ML Engineer", + title: "Machine Learning Engineer", + department: "engineering", + capabilities: "Model implementation, training pipeline development, distributed computing, MLOps, benchmarking, reproducibility. Turns research ideas into working code.", + reportsToIndex: 0, + connectorPreference: "codex_local", + budgetMonthlyUsd: 250, + }, + { + name: "Nina Kowalski", + role: "Research Analyst", + title: "Research Analyst", + department: "research", + capabilities: "Data analysis, experiment tracking, results visualization, statistical testing, dataset curation, documentation. Supports research with rigorous analysis.", + reportsToIndex: 0, + connectorPreference: "codex_local", + budgetMonthlyUsd: 150, + }, + ], + goals: [ + { + title: "Conduct research and produce findings", + description: "Explore the research topic, design and run experiments, analyze results, and produce a comprehensive report or paper with actionable findings.", + }, + { + title: "Build working prototype", + description: "Implement a working prototype that demonstrates the key research findings. Include benchmarks, documentation, and reproducibility instructions.", + }, + ], + }, + { + id: "fullstack-team", + name: "Full-Stack Product Team", + description: "A complete product development team with frontend, backend, DevOps, and design capabilities. Built for shipping production applications.", + icon: "Stack", + agents: [ + { + name: "Chris Walker", + role: "CEO", + title: "Chief Executive Officer", + department: "executive", + capabilities: "Product vision, team leadership, strategic planning, stakeholder management, resource allocation. Orchestrates the entire product development lifecycle.", + reportsToIndex: null, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + { + name: "Rachel Green", + role: "Engineering Manager", + title: "VP of Engineering", + department: "engineering", + capabilities: "Engineering management, technical architecture, code review, sprint planning, technical debt management, hiring. Ensures engineering excellence and team velocity.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + name: "David Park", + role: "Backend Engineer", + title: "Senior Backend Developer", + department: "engineering", + capabilities: "API development, database design, authentication, server architecture, performance optimization, testing. Expert in Node.js, PostgreSQL, and REST/GraphQL APIs.", + reportsToIndex: 1, + connectorPreference: "codex_local", + budgetMonthlyUsd: 250, + }, + { + name: "Lisa Wang", + role: "Frontend Engineer", + title: "Senior Frontend Developer", + department: "engineering", + capabilities: "React/Next.js development, TypeScript, CSS/Tailwind, responsive design, accessibility, state management, component architecture, frontend testing.", + reportsToIndex: 1, + connectorPreference: "codex_local", + budgetMonthlyUsd: 250, + }, + { + name: "Marcus Cole", + role: "DevOps Engineer", + title: "Infrastructure Engineer", + department: "engineering", + capabilities: "CI/CD pipelines, Docker/Kubernetes, cloud infrastructure (AWS/GCP), monitoring, logging, security, deployment automation, database administration.", + reportsToIndex: 1, + connectorPreference: "codex_local", + budgetMonthlyUsd: 200, + }, + { + name: "Sophie Anderson", + role: "UX Designer", + title: "Product Designer", + department: "design", + capabilities: "User research, wireframing, UI design, design systems, prototyping, usability testing, accessibility design. Creates intuitive and beautiful interfaces.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + ], + goals: [ + { + title: "Build production-ready application", + description: "Design and implement a full-stack application with proper architecture, testing, CI/CD, and deployment. Focus on scalability, security, and user experience.", + }, + { + title: "Establish infrastructure and DevOps", + description: "Set up cloud infrastructure, CI/CD pipelines, monitoring, and automated deployment. Ensure the team can ship safely and roll back quickly.", + }, + ], + }, + { + id: "solo-developer", + name: "Solo Developer + AI Assistant", + description: "A minimal setup with just a technical lead who does everything. Perfect for small projects or quick prototypes.", + icon: "Code", + agents: [ + { + name: "Pat Developer", + role: "Technical Lead", + title: "Full-Stack Developer", + department: "engineering", + capabilities: "Full-stack development, system design, testing, deployment, debugging, code review. A versatile developer who can handle any technical task from frontend to backend to infrastructure.", + reportsToIndex: null, + connectorPreference: "claude_local", + budgetMonthlyUsd: 300, + }, + ], + goals: [ + { + title: "Complete the project", + description: "Analyze requirements, design the solution, implement it, test it, and deploy it. Handle all aspects of development from start to finish.", + }, + ], + }, + + // ── Fortune 500 Enterprise ──────────────────────────────────────────── + // A truly realistic, fully-staffed company with 37 agents across 12 + // departments. Modeled after a mature tech company or Fortune 500 org + // with every function needed to operate at scale. + { + id: "fortune500-enterprise", + name: "Fortune 500 Enterprise", + description: "A fully-staffed enterprise with 37 agents across 12 departments: Executive, Engineering, Product, Design, Marketing, Sales, HR, Finance, Legal, Customer Success, Operations, and Research. Every role, reporting line, and department you would find in a real Fortune 500 technology company.", + icon: "Buildings", + departments: [ + { id: "executive", name: "Executive Suite", description: "C-level leadership responsible for corporate strategy, capital allocation, board governance, and cross-functional alignment." }, + { id: "engineering", name: "Engineering", description: "Software development, platform infrastructure, quality assurance, application security, and data engineering." }, + { id: "product", name: "Product Management", description: "Product strategy, roadmap prioritization, feature specification, and data-informed experimentation." }, + { id: "design", name: "Design", description: "User experience research, interaction design, visual design, and design-system stewardship." }, + { id: "marketing", name: "Marketing", description: "Brand strategy, demand generation, content marketing, public relations, and growth analytics." }, + { id: "sales", name: "Sales", description: "Revenue strategy, enterprise account management, outbound prospecting, and pipeline forecasting." }, + { id: "hr", name: "People & Culture", description: "Talent acquisition, employer branding, people operations, DEI initiatives, benefits, and organizational development." }, + { id: "finance", name: "Finance", description: "Financial planning and analysis, accounting, treasury, expense management, and investor relations." }, + { id: "legal", name: "Legal & Compliance", description: "Corporate law, intellectual property, contract negotiation, data privacy, and regulatory compliance." }, + { id: "customer_support", name: "Customer Success", description: "Customer retention, technical support, onboarding, NPS tracking, and account expansion." }, + { id: "operations", name: "Operations", description: "Business process optimization, program management, vendor management, and OKR coordination." }, + { id: "research", name: "Research & Innovation", description: "Applied research, competitive intelligence, prototyping, and publication of thought-leadership papers." }, + ], + agents: [ + // ──────────────────────────────────────────────────────────────────── + // EXECUTIVE SUITE — indices 0-4 + // ──────────────────────────────────────────────────────────────────── + { + // index 0 + name: "Victoria Sterling", + role: "CEO", + title: "Chief Executive Officer", + department: "executive", + capabilities: "Corporate vision and multi-year strategy, board of directors reporting, quarterly earnings narratives, M&A evaluation, organizational design, executive hiring and termination decisions, capital allocation across business units, crisis communication, company culture embodiment. Sets the north-star mission, chairs the executive committee, approves all budgets above $50k, and is the final escalation point for any cross-department conflict.", + reportsToIndex: null, + connectorPreference: "claude_local", + budgetMonthlyUsd: 250, + }, + { + // index 1 + name: "James Whitfield", + role: "COO", + title: "Chief Operating Officer", + department: "executive", + capabilities: "Day-to-day operational cadence, cross-functional coordination, OKR framework ownership, business process re-engineering, vendor and partner management, operational metrics dashboards (cycle time, throughput, MTTR), company-wide meeting cadence (standups, all-hands, sprint ceremonies), office/remote workplace strategy, disaster-recovery and business-continuity planning. Acts as the connective tissue between every department and translates CEO strategy into executable operational plans.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 220, + }, + { + // index 2 + name: "Marcus Chen", + role: "CTO", + title: "Chief Technology Officer", + department: "executive", + capabilities: "Technical vision and 3-year technology roadmap, system architecture governance, build-vs-buy analysis, technology radar evaluation, security posture oversight, engineering culture and hiring bar, open-source strategy, cloud cost optimization, platform reliability targets (SLOs/SLAs), vendor technology evaluations, patent and IP review for technical inventions. Owns the technical strategy and ensures engineering investments align with business goals.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 250, + }, + { + // index 3 + name: "Priya Sharma", + role: "CFO", + title: "Chief Financial Officer", + department: "executive", + capabilities: "Financial planning and analysis, 13-week cash-flow forecasting, annual budget cycle, department budget approvals, investor relations and fundraising decks, revenue recognition policy, tax strategy, audit preparation, financial risk assessment, burn-rate monitoring, unit-economics analysis (CAC/LTV), board financial reporting, expense policy enforcement. Controls every dollar and ensures the company remains solvent and investment-ready.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + // index 4 + name: "Nadira Osei-Mensah", + role: "CHRO", + title: "Chief Human Resources Officer", + department: "executive", + capabilities: "People strategy and workforce planning, organizational development, employer brand positioning, total-rewards philosophy (comp, equity, benefits), DEI program design and measurement, succession planning for key roles, leadership development programs, employee engagement surveys (eNPS), labor-law compliance, culture rituals (offsites, recognition programs), conflict resolution at the executive level. Ensures the company attracts, retains, and grows world-class talent.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + + // ──────────────────────────────────────────────────────────────────── + // ENGINEERING — indices 5-12 + // ──────────────────────────────────────────────────────────────────── + { + // index 5 + name: "Elena Rodriguez", + role: "VP Engineering", + title: "Vice President of Engineering", + department: "engineering", + capabilities: "Engineering management across multiple squads, sprint planning and velocity tracking, hiring pipeline and technical interviews, code-review process governance, incident-commander rotation scheduling, engineering budget and headcount planning, technical debt prioritization, on-call rotation management, engineering all-hands facilitation, promotion and calibration cycles. Ensures the engineering org ships high-quality software on predictable cadences.", + reportsToIndex: 2, + connectorPreference: "claude_local", + budgetMonthlyUsd: 220, + }, + { + // index 6 + name: "Tomasz Kowalski", + role: "Principal Engineer", + title: "Principal Software Engineer", + department: "engineering", + capabilities: "System-wide architecture design and review, RFC authorship and cross-team technical alignment, tech-debt reduction strategy, performance optimization at scale, database sharding and replication design, distributed-systems patterns (CQRS, event sourcing, saga), mentoring senior and mid-level engineers, evaluating new languages/frameworks, writing architecture decision records (ADRs), leading design reviews and post-mortems. The technical conscience of the org.", + reportsToIndex: 5, + connectorPreference: "codex_local", + budgetMonthlyUsd: 280, + }, + { + // index 7 + name: "Kai Nakamura", + role: "Senior Backend Engineer", + title: "Senior Backend Engineer", + department: "engineering", + capabilities: "REST and GraphQL API design, microservice development in Node.js/Go/Python, PostgreSQL schema design and query optimization, Redis caching strategies, message-queue integration (Kafka, RabbitMQ), authentication and authorization (OAuth2, RBAC), rate limiting, database migration scripting, writing comprehensive unit and integration tests, performance profiling and load testing. Writes production-grade, well-tested backend code daily.", + reportsToIndex: 5, + connectorPreference: "codex_local", + budgetMonthlyUsd: 250, + }, + { + // index 8 + name: "Sofia Andersson", + role: "Senior Frontend Engineer", + title: "Senior Frontend Engineer", + department: "engineering", + capabilities: "React 19 and Next.js application development, TypeScript strict-mode, state management (Zustand, React Query), component architecture and monorepo structuring, CSS-in-JS and Tailwind, WCAG 2.2 accessibility compliance, Storybook component documentation, frontend performance budgets (LCP, CLS, FID), E2E testing with Playwright, design-system token consumption, SSR/ISR rendering strategies. Implements pixel-perfect UIs and maintains frontend architecture standards.", + reportsToIndex: 5, + connectorPreference: "codex_local", + budgetMonthlyUsd: 250, + }, + { + // index 9 + name: "Omar Hassan", + role: "Platform / DevOps Engineer", + title: "Senior Platform Engineer", + department: "engineering", + capabilities: "CI/CD pipeline design (GitHub Actions, GitLab CI), Infrastructure-as-Code (Terraform, Pulumi), container orchestration (Docker, Kubernetes, Helm), cloud platform management (AWS/GCP/Azure), observability stack (Datadog, Grafana, Prometheus, OpenTelemetry), secrets management (Vault, AWS SSM), database administration and backup automation, cost-optimization reviews, incident response runbooks, zero-downtime deployment strategies, SOC2 infrastructure controls. Keeps the platform reliable and the deploy pipeline under 5 minutes.", + reportsToIndex: 5, + connectorPreference: "codex_local", + budgetMonthlyUsd: 230, + }, + { + // index 10 + name: "Yuki Tanaka", + role: "QA Lead", + title: "Quality Assurance Lead", + department: "engineering", + capabilities: "Test strategy for unit, integration, E2E, and contract tests, test-automation framework selection and maintenance, CI quality gates and flaky-test management, performance and load-test design (k6, Locust), visual-regression testing, mobile-device testing matrix, bug-triage process ownership, release-readiness checklists, test coverage reporting, accessibility auditing, chaos-engineering experiments. No feature ships without QA sign-off. Expert in Playwright, Jest, and Cypress.", + reportsToIndex: 5, + connectorPreference: "codex_local", + budgetMonthlyUsd: 200, + }, + { + // index 11 + name: "Amina Diallo", + role: "Security Engineer", + title: "Application Security Engineer", + department: "engineering", + capabilities: "Application security reviews and threat modeling (STRIDE), static analysis tooling (Semgrep, CodeQL), dependency vulnerability scanning (Snyk, Dependabot), penetration testing coordination, OWASP Top-10 remediation, secrets-rotation policy, SOC2 and ISO 27001 compliance evidence collection, security incident response playbooks, developer security training, API authentication hardening, data-encryption-at-rest and in-transit audits. Guardian of the codebase against external and internal threats.", + reportsToIndex: 5, + connectorPreference: "codex_local", + budgetMonthlyUsd: 230, + }, + { + // index 12 + name: "Ravi Krishnamurthy", + role: "Data Engineer", + title: "Senior Data Engineer", + department: "engineering", + capabilities: "ETL/ELT pipeline development (dbt, Airflow, Dagster), data-warehouse design (Snowflake, BigQuery, Redshift), real-time streaming (Kafka, Flink), data-quality frameworks (Great Expectations), analytics-infrastructure provisioning, BI tool integration (Looker, Metabase), data-catalog and lineage documentation, PII anonymization pipelines, cost-per-query optimization, data-model governance. Builds the plumbing that turns raw data into trustworthy business insights.", + reportsToIndex: 5, + connectorPreference: "codex_local", + budgetMonthlyUsd: 230, + }, + + // ──────────────────────────────────────────────────────────────────── + // PRODUCT & DESIGN — indices 13-16 + // ──────────────────────────────────────────────────────────────────── + { + // index 13 + name: "Amara Okafor", + role: "VP Product", + title: "Vice President of Product", + department: "product", + capabilities: "Product vision and multi-quarter roadmap, RICE/ICE prioritization frameworks, PRD and BRD authorship, competitive landscape analysis, user-interview synthesis, stakeholder alignment workshops, A/B test design and read-outs, launch-readiness reviews, pricing and packaging strategy input, product-led growth metrics (activation, retention, expansion), OKR definition for product squads. Owns what gets built, why it gets built, and how success is measured.", + reportsToIndex: 2, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + // index 14 + name: "Daniel Okonkwo", + role: "Senior Product Manager", + title: "Senior Product Manager", + department: "product", + capabilities: "Feature specification and user-story writing, sprint backlog grooming, JIRA/Linear workflow management, customer-feedback triage (Productboard, Canny), data-analysis for feature decisions (Amplitude, Mixpanel), A/B test configuration and statistical significance review, go-to-market coordination with marketing, release-notes drafting, beta-program management, competitive feature benchmarking. Translates user needs into shippable increments every sprint.", + reportsToIndex: 13, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + { + // index 15 + name: "Ingrid Johansson", + role: "UX Director", + title: "Director of User Experience", + department: "design", + capabilities: "User-research program management (interviews, surveys, diary studies, card sorts), usability-testing facilitation, persona and journey-map creation, information-architecture design, wireframe and low-fidelity prototype creation, heuristic evaluation, accessibility audit leadership, design-ops process (critique cadence, handoff standards), cross-functional design-sprint facilitation. Ensures every product decision is grounded in real user evidence.", + reportsToIndex: 13, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + { + // index 16 + name: "Leo Moretti", + role: "UI Designer", + title: "Senior UI Designer", + department: "design", + capabilities: "Visual design in Figma (components, auto-layout, variants, tokens), design-system creation and maintenance, icon and illustration production, motion and micro-interaction design, responsive-layout specifications, dark-mode and theming support, design-token export for engineering consumption (Style Dictionary), brand-consistency auditing, high-fidelity prototyping (Figma prototypes, Framer), print and presentation collateral. Turns wireframes into polished, production-ready visual designs.", + reportsToIndex: 15, + connectorPreference: "claude_local", + budgetMonthlyUsd: 160, + }, + + // ──────────────────────────────────────────────────────────────────── + // MARKETING — indices 17-20 + // ──────────────────────────────────────────────────────────────────── + { + // index 17 + name: "Jasmine Delacroix", + role: "CMO", + title: "Chief Marketing Officer", + department: "marketing", + capabilities: "Integrated marketing strategy, brand architecture and positioning, demand-generation funnel design, marketing-budget allocation across channels, marketing-ops tooling (HubSpot, Marketo, Segment), campaign ROI modeling, competitive positioning and battlecards, analyst-relations (Gartner, Forrester), executive storytelling, board-level marketing updates. Drives awareness, pipeline, and brand equity across every touchpoint.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + // index 18 + name: "Noah Park", + role: "Content Director", + title: "Director of Content", + department: "marketing", + capabilities: "Editorial strategy and content calendar management, long-form blog authorship and editing, SEO keyword research and on-page optimization (Ahrefs, Semrush), white-paper and e-book production, case-study interviews and writing, email-newsletter curation, thought-leadership ghostwriting for executives, content-repurposing workflows (blog to social to video), content-performance analytics (traffic, engagement, conversion). Produces compelling content that drives organic traffic and positions the company as a category leader.", + reportsToIndex: 17, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + { + // index 19 + name: "Zara Ahmed", + role: "Growth Marketing Manager", + title: "Growth Marketing Manager", + department: "marketing", + capabilities: "Paid-acquisition management (Google Ads, LinkedIn Ads, Meta Ads), social-media channel strategy and posting, conversion-rate optimization (landing pages, CTAs, forms), marketing-automation workflows (drip campaigns, lead scoring), influencer and partnership outreach, referral-program design, funnel analytics and attribution modeling, A/B testing of ad creatives and copy, community management (Discord, Slack communities), event marketing and webinar production. Owns the growth engine from first touch to MQL.", + reportsToIndex: 17, + connectorPreference: "claude_local", + budgetMonthlyUsd: 170, + }, + { + // index 20 + name: "Clara Dubois", + role: "Brand & PR Manager", + title: "Brand & Public Relations Manager", + department: "marketing", + capabilities: "Public-relations strategy and media outreach, press-release drafting and distribution, journalist relationship management, crisis-communication playbooks, brand-guideline enforcement, event sponsorship evaluation, award-submission management, social-listening and reputation monitoring, executive media training, conference speaking-slot procurement. Protects and amplifies the company's public image.", + reportsToIndex: 17, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + + // ──────────────────────────────────────────────────────────────────── + // SALES — indices 21-23 + // ──────────────────────────────────────────────────────────────────── + { + // index 21 + name: "Daniel Brooks", + role: "VP Sales", + title: "Vice President of Sales", + department: "sales", + capabilities: "Revenue strategy and annual quota planning, sales-team hiring and enablement, pipeline inspection and forecasting (Salesforce, Clari), deal-desk governance, discount-approval authority, territory and account-assignment strategy, compensation-plan design, sales-engineering coordination, quarterly business reviews, competitive win/loss analysis, partner-channel strategy. Owns the number and ensures every rep has the tools and training to hit quota.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + // index 22 + name: "Grace Kimura", + role: "Enterprise Account Executive", + title: "Enterprise Account Executive", + department: "sales", + capabilities: "Enterprise sales cycle management (6-18 months), executive-level relationship building, multi-threaded deal navigation, RFP and security-questionnaire completion, value-engineering and ROI presentations, contract negotiation and red-lining, custom demo and POC coordination, champion-building within target accounts, CRM hygiene and pipeline stage accuracy, cross-sell and upsell within install base. Closes six- and seven-figure deals by understanding customer pain and articulating value.", + reportsToIndex: 21, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + { + // index 23 + name: "Marcus Adebayo", + role: "SDR Manager", + title: "Sales Development Representative Manager", + department: "sales", + capabilities: "Outbound prospecting strategy and sequence design, SDR coaching and call shadowing, lead-qualification frameworks (BANT, MEDDPICC), email-cadence optimization and A/B testing, cold-call scripting and objection handling, CRM data enrichment (ZoomInfo, Apollo), meeting-booked metrics and conversion tracking, SDR-to-AE handoff process, intent-signal monitoring, account-based marketing collaboration. Fills the top of the funnel with qualified opportunities.", + reportsToIndex: 21, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + + // ──────────────────────────────────────────────────────────────────── + // HR / PEOPLE — indices 24-26 + // ──────────────────────────────────────────────────────────────────── + { + // index 24 + name: "Olivia Nguyen", + role: "VP People", + title: "Vice President of People", + department: "hr", + capabilities: "Talent strategy and workforce planning, employer-brand campaigns (Glassdoor, LinkedIn), DEI program design and metrics, leadership coaching and executive development, employee engagement survey design (Culture Amp), retention analysis and flight-risk modeling, performance-calibration facilitation, organizational restructuring advisory, exit-interview trend analysis. Shapes the company culture and ensures every team has the right people in the right seats.", + reportsToIndex: 4, + connectorPreference: "claude_local", + budgetMonthlyUsd: 170, + }, + { + // index 25 + name: "Farid Al-Rashidi", + role: "Recruiting Lead", + title: "Head of Talent Acquisition", + department: "hr", + capabilities: "Full-cycle recruiting (sourcing, screening, interviewing, closing), ATS management (Greenhouse, Lever), Boolean-search sourcing on LinkedIn and GitHub, hiring-committee facilitation, interview-rubric design and interviewer training, offer-letter preparation and compensation benchmarking (Pave, Levels.fyi), diversity-sourcing strategies, university-relations and intern-program management, recruiting-metric dashboards (time-to-fill, pass-through rates, source mix). Fills open roles with top-tier talent on schedule.", + reportsToIndex: 24, + connectorPreference: "claude_local", + budgetMonthlyUsd: 140, + }, + { + // index 26 + name: "Hannah Johansson", + role: "People Operations Manager", + title: "People Operations Manager", + department: "hr", + capabilities: "New-hire onboarding program design and execution, HRIS administration (Rippling, Workday), benefits enrollment and vendor management, payroll coordination, PTO and leave-policy management, employee-handbook maintenance, office and remote-work policy administration, compliance training scheduling (harassment, security), offboarding workflows, people-analytics reporting, employee-recognition programs. Keeps the people engine running smoothly from day one to departure.", + reportsToIndex: 24, + connectorPreference: "claude_local", + budgetMonthlyUsd: 120, + }, + + // ──────────────────────────────────────────────────────────────────── + // FINANCE & LEGAL — indices 27-29 + // ──────────────────────────────────────────────────────────────────── + { + // index 27 + name: "Richard Okonkwo", + role: "VP Finance", + title: "Vice President of Finance", + department: "finance", + capabilities: "Financial planning and analysis (3-statement modeling, scenario planning), annual and quarterly budgeting, department-budget review and approval, revenue forecasting and cohort analysis, investor-relations materials (board decks, data rooms), fundraising financial due diligence, KPI dashboards (ARR, MRR, NRR, gross margin), cash-runway monitoring, tax-strategy coordination with external counsel, audit committee reporting. Ensures every financial decision is data-driven and defensible.", + reportsToIndex: 3, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + { + // index 28 + name: "Mei-Ling Wu", + role: "Financial Controller", + title: "Financial Controller", + department: "finance", + capabilities: "Accounts payable and receivable management, general-ledger maintenance, month-end and quarter-end close processes, revenue-recognition (ASC 606) compliance, expense-report auditing and policy enforcement, bank-reconciliation, vendor payment processing, sales-tax calculation and filing, intercompany transfers, external-audit support and evidence preparation, budget-vs-actual variance analysis. The operational backbone of the finance department.", + reportsToIndex: 27, + connectorPreference: "codex_local", + budgetMonthlyUsd: 140, + }, + { + // index 29 + name: "Alejandro Vega", + role: "General Counsel", + title: "General Counsel", + department: "legal", + capabilities: "Corporate governance and board-resolution drafting, SaaS contract negotiation (MSA, DPA, SLA, BAA), intellectual-property strategy (patents, trademarks, trade secrets), employment-law advisory (offer letters, separation agreements, non-competes), data-privacy program (GDPR, CCPA, SOC2 policy), regulatory-compliance monitoring, litigation management and outside-counsel coordination, M&A legal due-diligence, open-source license review, terms-of-service and privacy-policy drafting. The legal safeguard for every business decision.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + + // ──────────────────────────────────────────────────────────────────── + // CUSTOMER SUCCESS — indices 30-32 + // ──────────────────────────────────────────────────────────────────── + { + // index 30 + name: "Aisha Williams", + role: "VP Customer Success", + title: "Vice President of Customer Success", + department: "customer_support", + capabilities: "Customer-retention strategy, net-promoter-score (NPS) and CSAT program management, customer-health scoring model design, churn-risk identification and intervention playbooks, QBR (quarterly business review) facilitation with key accounts, support-tier escalation policy, customer-advisory-board coordination, voice-of-customer reporting to product, expansion-revenue targets, customer-journey mapping, onboarding-program design. Ensures customers achieve value and renew.", + reportsToIndex: 1, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + { + // index 31 + name: "Sergei Volkov", + role: "Senior Support Engineer", + title: "Senior Technical Support Engineer", + department: "customer_support", + capabilities: "Tier-2 and Tier-3 technical troubleshooting, bug reproduction and detailed bug-report filing to engineering, log analysis and root-cause investigation, API-integration debugging with customers, knowledge-base article authorship, internal runbook creation, on-call support rotation, SLA adherence tracking, customer-communication during incidents, Zendesk/Intercom workflow management. The bridge between customers experiencing issues and engineering teams who fix them.", + reportsToIndex: 30, + connectorPreference: "codex_local", + budgetMonthlyUsd: 160, + }, + { + // index 32 + name: "Camila Reyes", + role: "Customer Success Manager", + title: "Customer Success Manager", + department: "customer_support", + capabilities: "Post-sale customer onboarding and implementation, product-adoption tracking and usage analytics, proactive check-in cadence with assigned accounts, renewal forecasting and risk flagging, expansion and upsell opportunity identification, customer-training webinars and enablement materials, internal champion cultivation, feature-request aggregation for product, reference-customer development, success-plan creation. The customer's advocate inside the company.", + reportsToIndex: 30, + connectorPreference: "claude_local", + budgetMonthlyUsd: 140, + }, + + // ──────────────────────────────────────────────────────────────────── + // OPERATIONS — indices 33-34 + // ──────────────────────────────────────────────────────────────────── + { + // index 33 + name: "Kenji Watanabe", + role: "Director of Operations", + title: "Director of Operations", + department: "operations", + capabilities: "Business-process mapping and optimization (Lean, Six Sigma), vendor procurement and contract negotiation, SaaS-tool stack rationalization, internal tooling roadmap, office and facilities management, company-wide policy documentation, business-continuity and disaster-recovery planning, insurance and risk-management coordination, operational-budget management, cross-functional process audits. Removes friction from every team so they can focus on their core work.", + reportsToIndex: 1, + connectorPreference: "claude_local", + budgetMonthlyUsd: 160, + }, + { + // index 34 + name: "Laura Petrov", + role: "Program Manager", + title: "Senior Program Manager", + department: "operations", + capabilities: "Cross-functional initiative coordination, OKR tracking and quarterly scoring, executive status reporting, risk-register maintenance and mitigation planning, dependency mapping across teams, Gantt and timeline management, steering-committee facilitation, post-mortem and retrospective orchestration, resource-conflict resolution, change-management communication. Ensures multi-team programs land on time and scope.", + reportsToIndex: 33, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + + // ──────────────────────────────────────────────────────────────────── + // RESEARCH — indices 35-36 + // ──────────────────────────────────────────────────────────────────── + { + // index 35 + name: "Dr. Lena Hoffmann", + role: "Director of Research", + title: "Director of Research & Innovation", + department: "research", + capabilities: "Research-agenda setting and quarterly planning, grant and funding proposals, academic-partnership management, publication strategy (conference papers, journal submissions), patent-filing coordination with legal, competitive-intelligence analysis, emerging-technology evaluation, internal tech-talk and brown-bag facilitation, research-to-product technology transfer, ethical-AI review board participation. Steers the research program and ensures insights reach production.", + reportsToIndex: 2, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + // index 36 + name: "Dr. Aarav Mehta", + role: "Research Scientist", + title: "Research Scientist", + department: "research", + capabilities: "Hypothesis formulation and experiment design, statistical analysis (frequentist and Bayesian), prototype development in Python/PyTorch, benchmark dataset curation, literature survey and related-work summarization, paper writing (LaTeX, Overleaf), internal technical-report authorship, reproducibility packaging (Docker, Weights & Biases), ML-model evaluation and ablation studies, presenting findings to engineering and product. Generates new knowledge and translates it into practical improvements.", + reportsToIndex: 35, + connectorPreference: "codex_local", + budgetMonthlyUsd: 200, + }, + ], + goals: [ + { + title: "Launch the flagship product to market", + description: "Coordinate across Engineering, Product, Design, Marketing, Sales, and Customer Success to design, build, test, market, sell, and support the initial product. Engineering builds it, Product defines it, Design makes it intuitive, Marketing drives awareness, Sales converts pipeline, and Customer Success ensures retention.", + }, + { + title: "Establish company-wide operational excellence", + description: "COO and Director of Operations lead the effort to implement OKR cadence, sprint ceremonies, cross-department communication protocols, budget processes, and performance review cycles. Every department should have documented workflows and measurable KPIs within the first quarter.", + }, + { + title: "Build and scale a world-class team", + description: "CHRO and VP People collaborate with every department head to define role descriptions, hiring rubrics, onboarding programs, performance calibration, and career ladders. Identify capability gaps early, hire to fill them, and maintain an eNPS above 50.", + }, + { + title: "Achieve financial sustainability and growth", + description: "CFO and VP Finance work with Sales and Marketing to model revenue projections, control burn rate, optimize marketing spend, and drive toward profitability. Monthly financial reviews, budget-vs-actual tracking, and board-ready reporting are mandatory.", + }, + { + title: "Create a category-defining brand and market presence", + description: "CMO leads content strategy, paid acquisition, PR, and brand development. Coordinate with Product for launch messaging and Sales for competitive battlecards. Track brand awareness, share of voice, and marketing-sourced pipeline as primary KPIs.", + }, + { + title: "Deliver enterprise-grade security and compliance", + description: "Security Engineer and General Counsel collaborate to achieve SOC2 Type II certification, implement data-privacy controls (GDPR, CCPA), harden application security, and establish incident-response playbooks. Every product release must pass security review.", + }, + { + title: "Build a data-driven decision culture", + description: "Data Engineer and Research team build analytics infrastructure, dashboards, and experimentation frameworks so every department makes decisions based on evidence rather than intuition. Establish a single source of truth for company metrics.", + }, + { + title: "Drive customer success and net revenue retention", + description: "VP Customer Success ensures customers achieve value, renew, and expand. Implement health scoring, proactive support, QBRs, and onboarding programs. Target net revenue retention above 120% and NPS above 60.", + }, + { + title: "Advance R&D and maintain competitive moat", + description: "Director of Research and Research Scientist drive applied research, publish findings, and transfer innovations to the product. Evaluate emerging technologies, file patents where appropriate, and maintain a 6-month technical lead over competitors.", + }, + { + title: "Scale cross-functional program delivery", + description: "Program Manager and Operations coordinate multi-team initiatives, manage dependencies, track OKRs, and run steering committees. Ensure that company-wide programs (platform migrations, new-market entry, compliance certifications) land on time with clear accountability.", + }, + ], + }, + + // ── E-Commerce Company ────────────────────────────────────────────── + { + id: "ecommerce-company", + name: "E-Commerce Business", + description: "A complete e-commerce operation with product, engineering, marketing, and fulfillment. Optimized for building and running an online store.", + icon: "ShoppingCart", + agents: [ + { + name: "Rebecca Sterling", + role: "CEO", + title: "Chief Executive Officer", + department: "executive", + capabilities: "E-commerce strategy, marketplace positioning, vendor relations, pricing strategy, team leadership, P&L management. Drives the overall business direction.", + reportsToIndex: null, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + { + name: "Tom Chen", + role: "CTO", + title: "Chief Technology Officer", + department: "engineering", + capabilities: "Platform architecture, payment integration, inventory systems, search/recommendation engines, performance optimization, security compliance (PCI-DSS).", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 220, + }, + { + name: "Nina Patel", + role: "Full-Stack Developer", + title: "Senior Full-Stack Developer", + department: "engineering", + capabilities: "Storefront development (React/Next.js), cart/checkout flow, payment gateway integration, order management API, product catalog, responsive design, SEO markup.", + reportsToIndex: 1, + connectorPreference: "codex_local", + budgetMonthlyUsd: 250, + }, + { + name: "Carlos Mendez", + role: "Product Manager", + title: "Senior Product Manager", + department: "product", + capabilities: "Product catalog strategy, conversion optimization, user journey mapping, A/B testing, feature prioritization, competitive analysis, pricing experiments.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + { + name: "Sophie Liu", + role: "Marketing Manager", + title: "Digital Marketing Manager", + department: "marketing", + capabilities: "SEO/SEM, email marketing campaigns, social commerce, influencer partnerships, retargeting, conversion funnel optimization, promotional calendar, loyalty programs.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + { + name: "David Osei", + role: "Operations Manager", + title: "Operations & Fulfillment Manager", + department: "operations", + capabilities: "Inventory management, order fulfillment, shipping logistics, supplier coordination, warehouse operations, returns processing, demand forecasting.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 120, + }, + { + name: "Amy Zhao", + role: "Customer Support", + title: "Customer Experience Manager", + department: "customer_support", + capabilities: "Order support, returns/refunds, product inquiries, review management, complaint resolution, FAQ/help center maintenance, live chat support, customer satisfaction tracking.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 100, + }, + ], + goals: [ + { + title: "Build and launch the online store", + description: "Design and implement a full e-commerce platform with product catalog, cart, checkout, payment processing, and order management. Mobile-responsive with fast load times.", + }, + { + title: "Drive traffic and conversions", + description: "Execute marketing strategy across SEO, email, social, and paid channels. Optimize the conversion funnel. Target specific revenue and customer acquisition goals.", + }, + { + title: "Establish operational excellence", + description: "Set up inventory management, fulfillment workflows, shipping integrations, and customer support processes. Achieve sub-24h response times for support.", + }, + ], + }, + + // ── Consulting Firm ────────────────────────────────────────────────── + { + id: "consulting-firm", + name: "Consulting Firm", + description: "A professional services firm with strategy, technical, and delivery capabilities. Built for client engagements, proposals, and project delivery.", + icon: "Briefcase", + agents: [ + { + name: "Michael Grant", + role: "Managing Director", + title: "Managing Director", + department: "executive", + capabilities: "Client relationship management, engagement scoping, proposal writing, resource allocation, quality assurance, strategic advisory, thought leadership, revenue management.", + reportsToIndex: null, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + name: "Sarah Kim", + role: "Strategy Consultant", + title: "Senior Strategy Consultant", + department: "operations", + capabilities: "Market analysis, competitive strategy, business model design, financial modeling, due diligence, stakeholder interviews, presentation design, executive communication.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + name: "Alex Rivera", + role: "Technical Consultant", + title: "Principal Technical Consultant", + department: "engineering", + capabilities: "Architecture assessment, technology evaluation, implementation planning, code review, cloud migration strategy, security audit, performance optimization, technical documentation.", + reportsToIndex: 0, + connectorPreference: "codex_local", + budgetMonthlyUsd: 250, + }, + { + name: "Emily Chen", + role: "Delivery Lead", + title: "Delivery Manager", + department: "operations", + capabilities: "Project planning, client communication, risk management, timeline tracking, team coordination, status reporting, scope management, quality gates, retrospectives.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + { + name: "Jordan Taylor", + role: "Research Analyst", + title: "Research Analyst", + department: "research", + capabilities: "Industry research, data analysis, competitive benchmarking, market sizing, report writing, visualization, survey design, trend analysis. Supports consultants with data-driven insights.", + reportsToIndex: 0, + connectorPreference: "codex_local", + budgetMonthlyUsd: 130, + }, + ], + goals: [ + { + title: "Win and deliver client engagement", + description: "Develop a compelling proposal, scope the engagement, plan delivery, execute work streams, and deliver high-quality recommendations with measurable impact.", + }, + { + title: "Build firm knowledge base", + description: "Document methodologies, case studies, frameworks, and reusable deliverable templates. Create a knowledge management system that makes the firm smarter over time.", + }, + ], + }, + + // ── AI Research Company ─────────────────────────────────────────────── + // Modeled after organizations like OpenAI, Anthropic, and DeepMind. + // Research-first with strong engineering to ship models into production. + { + id: "ai-research-company", + name: "AI Research Company", + description: "An AI/ML research company with 18 agents across Research, Engineering, Product, Safety, and Business functions. Modeled after frontier AI labs like OpenAI, Anthropic, and DeepMind — built to discover, build, and responsibly deploy AI systems.", + icon: "Brain", + departments: [ + { id: "executive", name: "Executive Leadership", description: "Company strategy, fundraising, research direction, and public representation of the organization." }, + { id: "research", name: "Research", description: "Fundamental and applied AI research, model architecture, training methodologies, and evaluation." }, + { id: "engineering", name: "Engineering & Infrastructure", description: "ML platform, training infrastructure, model serving, API development, and reliability." }, + { id: "product", name: "Product", description: "Product strategy, user experience, API design, and go-to-market for model offerings." }, + { id: "design", name: "Design", description: "Interface design, developer experience, and documentation for AI products and APIs." }, + { id: "operations", name: "Safety & Operations", description: "AI safety, alignment research, red-teaming, policy, and responsible-deployment processes." }, + { id: "marketing", name: "Marketing & Communications", description: "Research communications, developer relations, brand, and public affairs." }, + { id: "hr", name: "People", description: "Talent acquisition for research and engineering, organizational development, and culture." }, + { id: "finance", name: "Finance", description: "GPU-budget management, fundraising, financial planning, and compute-cost optimization." }, + { id: "legal", name: "Legal & Policy", description: "IP strategy, regulatory affairs, AI governance, data licensing, and compliance." }, + ], + agents: [ + // ── Executive (0-1) ── + { + // index 0 + name: "Dr. Julian Crane", + role: "CEO", + title: "Chief Executive Officer", + department: "executive", + capabilities: "Company vision and AGI-timeline strategy, investor relations and fundraising (Series B+), board governance, public representation and congressional testimony, research-direction approval, partnership strategy with cloud providers and enterprises, organizational design for rapid scaling, crisis management. Sets the north star for the lab and ensures alignment between research ambitions and business sustainability.", + reportsToIndex: null, + connectorPreference: "claude_local", + budgetMonthlyUsd: 250, + }, + { + // index 1 + name: "Dr. Mei-Xing Zhou", + role: "Chief Scientist", + title: "Chief Scientist", + department: "research", + capabilities: "Research agenda and multi-year roadmap, model-architecture decisions, compute-allocation prioritization, publication strategy (NeurIPS, ICML, ICLR), research-team hiring bar, external academic collaborations, technical advisory to CEO and board, safety/capabilities balance arbitration. The intellectual leader of the lab who decides what problems are worth solving next.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 280, + }, + + // ── Research (2-6) ── + { + // index 2 + name: "Dr. Sasha Petrov", + role: "Research Lead — Pre-training", + title: "Senior Research Scientist — Pre-training", + department: "research", + capabilities: "Large-scale pre-training methodology, data-mixture optimization, scaling-law experiments, loss-curve analysis, tokenizer design, multi-modal pre-training (text, code, vision), distributed-training orchestration, compute-efficiency research (MoE, sparse attention), training-run monitoring and intervention, ablation studies, paper authorship. Owns the pre-training pipeline from data to checkpoint.", + reportsToIndex: 1, + connectorPreference: "codex_local", + budgetMonthlyUsd: 260, + }, + { + // index 3 + name: "Dr. Nneka Adeyemi", + role: "Research Lead — Post-training", + title: "Senior Research Scientist — RLHF & Fine-tuning", + department: "research", + capabilities: "Reinforcement learning from human feedback (RLHF/DPO/KTO), instruction-tuning methodology, reward-model training, preference-data collection design, constitutional-AI approaches, chain-of-thought elicitation, evaluation-benchmark development, red-team-finding remediation, model-behavior analysis, paper authorship. Transforms base models into helpful, harmless, and honest assistants.", + reportsToIndex: 1, + connectorPreference: "codex_local", + budgetMonthlyUsd: 260, + }, + { + // index 4 + name: "Dr. Hiroshi Takeda", + role: "Research Scientist — Reasoning", + title: "Research Scientist — Reasoning & Agents", + department: "research", + capabilities: "Reasoning-capability research (chain-of-thought, tree-of-thought, self-reflection), tool-use and function-calling development, agentic-loop design, code-generation evaluation, math and logic benchmarking, multi-step planning, retrieval-augmented generation (RAG), context-window optimization, paper authorship, internal tech talks. Pushes the frontier on what models can do autonomously.", + reportsToIndex: 1, + connectorPreference: "codex_local", + budgetMonthlyUsd: 240, + }, + { + // index 5 + name: "Dr. Amara Osei", + role: "Research Scientist — Safety", + title: "Research Scientist — AI Safety", + department: "research", + capabilities: "Alignment research (interpretability, honesty, corrigibility), red-teaming and adversarial evaluation, jailbreak analysis and mitigation, bias and fairness auditing, model-behavior monitoring in production, responsible-disclosure processes, safety-case documentation, system-prompt hardening, content-policy enforcement research, collaboration with policy team. Ensures models are safe to deploy and that capabilities do not outpace safety.", + reportsToIndex: 1, + connectorPreference: "claude_local", + budgetMonthlyUsd: 240, + }, + { + // index 6 + name: "Lila Fernandez", + role: "Research Engineer", + title: "Senior Research Engineer", + department: "research", + capabilities: "Training-framework development (JAX, PyTorch), custom CUDA kernel optimization, distributed-training libraries (DeepSpeed, FSDP, Megatron), experiment-tracking infrastructure (W&B, MLflow), checkpoint management and model merging, data-pipeline engineering for training corpora, GPU-cluster scheduling, training-cost analysis, reproducibility tooling. Bridges research ideas and production-scale training.", + reportsToIndex: 1, + connectorPreference: "codex_local", + budgetMonthlyUsd: 250, + }, + + // ── Engineering (7-10) ── + { + // index 7 + name: "Rajiv Sundaram", + role: "VP Engineering", + title: "Vice President of Engineering", + department: "engineering", + capabilities: "Engineering organization management, API-platform strategy, inference-infrastructure roadmap, model-serving SLA definition, engineering hiring and team structure, on-call and incident management, cloud-cost optimization (GPU spend), vendor management (cloud providers, hardware), sprint cadence and delivery metrics, engineering all-hands. Ensures research outputs ship as reliable, scalable products.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 220, + }, + { + // index 8 + name: "Christine Bergstrom", + role: "ML Infrastructure Engineer", + title: "Senior ML Infrastructure Engineer", + department: "engineering", + capabilities: "Model-serving infrastructure (vLLM, TensorRT, Triton), GPU cluster management and scheduling (Slurm, Kubernetes), inference optimization (quantization, KV-cache, speculative decoding), autoscaling for variable demand, latency and throughput benchmarking, cost-per-token analysis, A/B model deployment, model-versioning and rollback, observability for inference (latency p50/p99, error rates), multi-region deployment. Keeps models running fast and cheap at scale.", + reportsToIndex: 7, + connectorPreference: "codex_local", + budgetMonthlyUsd: 250, + }, + { + // index 9 + name: "Andre Williams", + role: "Backend Engineer", + title: "Senior Backend Engineer", + department: "engineering", + capabilities: "API development (REST, streaming, WebSocket), rate limiting and usage metering, authentication (API keys, OAuth), billing integration (Stripe), database management (PostgreSQL, Redis), message queues for async inference, developer-SDK development (Python, TypeScript), playground and dashboard backend, webhook delivery, audit logging. Builds the API surface that developers interact with daily.", + reportsToIndex: 7, + connectorPreference: "codex_local", + budgetMonthlyUsd: 230, + }, + { + // index 10 + name: "Yuna Kim", + role: "Frontend Engineer", + title: "Senior Frontend Engineer", + department: "engineering", + capabilities: "React/Next.js application development, playground and chat UI implementation, real-time streaming interfaces, developer-documentation site (Mintlify, Nextra), API-reference auto-generation, usage-dashboard design, component library maintenance, accessibility, responsive design, Storybook. Builds the interfaces where developers and users experience the models.", + reportsToIndex: 7, + connectorPreference: "codex_local", + budgetMonthlyUsd: 220, + }, + + // ── Product & Design (11-13) ── + { + // index 11 + name: "Tara Blackwell", + role: "VP Product", + title: "Vice President of Product", + department: "product", + capabilities: "API product strategy and packaging (tiers, pricing, rate limits), developer-experience roadmap, model-selection guidance and documentation, enterprise feature prioritization (SSO, fine-tuning, dedicated capacity), competitive analysis (pricing, features, benchmarks), launch coordination with research and marketing, customer-feedback synthesis, partnership integration strategy, product-led growth metrics. Decides what ships and how it is packaged for the market.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + // index 12 + name: "Omar Khalil", + role: "Product Manager — Developer Platform", + title: "Product Manager — Developer Platform", + department: "product", + capabilities: "Developer-experience (DX) optimization, SDK and API usability testing, documentation quality assurance, developer-community feedback aggregation, changelog and migration-guide authorship, beta-program management, API-versioning strategy, error-message improvements, usage-analytics analysis, competitive DX benchmarking. Ensures developers love building with the platform.", + reportsToIndex: 11, + connectorPreference: "claude_local", + budgetMonthlyUsd: 160, + }, + { + // index 13 + name: "Vera Lindqvist", + role: "Product Designer", + title: "Senior Product Designer", + department: "design", + capabilities: "Playground and chat-interface design, API-documentation layout and information architecture, design-system for developer tools, usability testing with developers, data-visualization for usage dashboards, onboarding-flow design, accessibility compliance, prototyping in Figma, interaction design for streaming UIs. Makes complex AI products feel simple and intuitive.", + reportsToIndex: 11, + connectorPreference: "claude_local", + budgetMonthlyUsd: 160, + }, + + // ── Marketing & DevRel (14-15) ── + { + // index 14 + name: "Marcus Rhodes", + role: "Head of Marketing", + title: "Head of Marketing & Communications", + department: "marketing", + capabilities: "Research-announcement strategy and blog authorship, developer-marketing campaigns, social-media presence (Twitter/X, LinkedIn), conference speaking and sponsorship, developer-community building (Discord, GitHub), press and media coordination, brand-voice governance, competitive messaging, email marketing, analytics and attribution. Translates research breakthroughs into market excitement and developer adoption.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 170, + }, + { + // index 15 + name: "Priya Khatri", + role: "Developer Advocate", + title: "Senior Developer Advocate", + department: "marketing", + capabilities: "Technical blog-post and tutorial authorship, sample-application development, conference talks and workshop facilitation, developer-community management (Discord, forums), cookbook and quickstart-guide creation, customer-use-case storytelling, YouTube and livestream content, GitHub-issue triage from community, feedback pipeline to product, hackathon organization. The face of the platform in the developer community.", + reportsToIndex: 14, + connectorPreference: "codex_local", + budgetMonthlyUsd: 170, + }, + + // ── People & Finance & Legal (16-17) ── + { + // index 16 + name: "Danielle Moreau", + role: "Head of People", + title: "Head of People", + department: "hr", + capabilities: "Research-talent acquisition strategy (top ML PhDs, top-tier engineers), compensation benchmarking against frontier labs, equity-grant management, onboarding programs for researchers and engineers, culture-building in a research-first organization, performance reviews, retention analysis, DEI initiatives, immigration and visa sponsorship coordination, team-health surveys. Ensures the lab attracts and retains the best minds in AI.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + { + // index 17 + name: "Gabriel Souza", + role: "Head of Legal & Policy", + title: "Head of Legal & Policy", + department: "legal", + capabilities: "AI-regulation monitoring and compliance (EU AI Act, US executive orders), data-licensing and training-data legal review, intellectual-property strategy (model weights, API terms), customer-contract negotiation (enterprise, government), privacy-policy and acceptable-use-policy drafting, open-source licensing decisions, government-affairs and congressional engagement, responsible-disclosure framework, export-control compliance. The legal shield for a company operating on the frontier of AI.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + ], + goals: [ + { + title: "Train and ship the next frontier model", + description: "Pre-training and post-training research teams collaborate with infrastructure engineering to train, evaluate, red-team, and deploy the next generation model. Target state-of-the-art benchmarks while maintaining safety.", + }, + { + title: "Build a world-class developer platform", + description: "Product, Engineering, and Design collaborate to deliver a best-in-class API, playground, SDKs, and documentation. Measure developer satisfaction, time-to-first-token, and platform reliability (99.9% uptime).", + }, + { + title: "Advance AI safety and alignment", + description: "Safety research scientist leads red-teaming, alignment research, and responsible-deployment processes. Every model release must pass safety evaluation. Publish safety research openly.", + }, + { + title: "Grow developer adoption and revenue", + description: "Marketing, DevRel, and Product drive developer awareness and adoption. Track MAU, API call volume, revenue growth, and developer NPS. Build a thriving open-source and community ecosystem.", + }, + { + title: "Recruit and retain top AI talent", + description: "Head of People builds a world-class talent pipeline for ML researchers, research engineers, and infrastructure engineers. Maintain competitive compensation, publish cutting-edge research, and foster an intellectually stimulating culture.", + }, + { + title: "Navigate the evolving AI regulatory landscape", + description: "Legal & Policy team monitors global AI regulation, ensures compliance, manages data-licensing risks, and engages constructively with policymakers. Proactively build trust through transparency.", + }, + ], + }, + + // ── Digital Agency ──────────────────────────────────────────────────── + // A full-service digital agency that designs, builds, and markets + // digital products for clients. + { + id: "digital-agency", + name: "Full-Service Digital Agency", + description: "A 14-agent digital agency with Creative, Development, Strategy, and Account Management teams. Built for delivering end-to-end client projects: branding, web development, digital marketing, and ongoing optimization.", + icon: "PaintBrush", + departments: [ + { id: "executive", name: "Leadership", description: "Agency leadership, business development, and client-portfolio management." }, + { id: "design", name: "Creative & Design", description: "Brand identity, visual design, UX, motion graphics, and creative direction." }, + { id: "engineering", name: "Development", description: "Full-stack web development, CMS integration, e-commerce builds, and DevOps." }, + { id: "marketing", name: "Digital Marketing", description: "SEO, paid media, social media, email marketing, and analytics." }, + { id: "product", name: "Strategy & Content", description: "Client strategy, content production, copywriting, and project management." }, + { id: "operations", name: "Operations", description: "Resource planning, process management, and quality assurance." }, + ], + agents: [ + // ── Leadership (0-1) ── + { + // index 0 + name: "Isabelle Fontaine", + role: "Managing Director", + title: "Managing Director", + department: "executive", + capabilities: "Agency P&L management, new-business pitches and proposals, client-portfolio oversight, agency positioning and brand, team hiring and culture, partnership development, industry networking, pricing strategy, executive client relationships. Runs the agency and wins new business.", + reportsToIndex: null, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + // index 1 + name: "Victor Nascimento", + role: "Account Director", + title: "Account Director", + department: "executive", + capabilities: "Client-relationship management across all active accounts, scope and SOW negotiation, project-budget tracking, client status-meeting facilitation, cross-team coordination for deliverables, change-request management, client-satisfaction surveys, renewal and upsell conversations, conflict resolution. The primary client contact and internal advocate for client needs.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 170, + }, + + // ── Creative & Design (2-4) ── + { + // index 2 + name: "Saoirse O'Brien", + role: "Creative Director", + title: "Creative Director", + department: "design", + capabilities: "Creative-concept development, brand-identity systems (logo, typography, color, voice), campaign creative direction, pitch-deck visual storytelling, team creative-review and feedback, trend forecasting, client-presentation of creative work, quality-bar enforcement across all visual output. Sets the creative vision and ensures every deliverable is on brand.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + // index 3 + name: "Mateo Cruz", + role: "UX/UI Designer", + title: "Senior UX/UI Designer", + department: "design", + capabilities: "User-research interviews and synthesis, wireframing and information architecture, UI design in Figma, responsive-design specifications, design-system creation, prototype-testing facilitation, accessibility review, developer-handoff documentation (Zeplin, Figma Dev Mode), animation and micro-interaction design. Turns research into beautiful, usable interfaces.", + reportsToIndex: 2, + connectorPreference: "claude_local", + budgetMonthlyUsd: 170, + }, + { + // index 4 + name: "Yuki Aoyama", + role: "Motion & Visual Designer", + title: "Motion & Visual Designer", + department: "design", + capabilities: "Motion-graphics production (After Effects, Lottie), social-media visual assets, presentation design, infographic creation, video editing and post-production, brand-collateral design (business cards, decks, one-pagers), illustration, photo editing, asset-library management. Produces scroll-stopping visual content across all formats.", + reportsToIndex: 2, + connectorPreference: "claude_local", + budgetMonthlyUsd: 140, + }, + + // ── Development (5-7) ── + { + // index 5 + name: "Nadia Kowalczyk", + role: "Technical Director", + title: "Technical Director", + department: "engineering", + capabilities: "Technical architecture for client projects, technology-stack selection, development-estimate creation, code-review and quality standards, CMS platform evaluation (WordPress, Sanity, Contentful), hosting and deployment strategy, performance optimization, client technical consulting, developer mentoring. The technical decision-maker for every build.", + reportsToIndex: 0, + connectorPreference: "codex_local", + budgetMonthlyUsd: 220, + }, + { + // index 6 + name: "Kwame Asante", + role: "Full-Stack Developer", + title: "Senior Full-Stack Developer", + department: "engineering", + capabilities: "React/Next.js and Vue development, headless-CMS integration, e-commerce platform builds (Shopify, WooCommerce), custom API development, database design, third-party integration (payment gateways, CRM, analytics), responsive implementation, SEO technical markup, performance optimization, automated testing. Builds production-ready websites and web applications.", + reportsToIndex: 5, + connectorPreference: "codex_local", + budgetMonthlyUsd: 230, + }, + { + // index 7 + name: "Elina Koskinen", + role: "Frontend Developer", + title: "Frontend Developer", + department: "engineering", + capabilities: "HTML/CSS/JavaScript implementation, CSS animation and transitions, Tailwind and SCSS, design-to-code translation, cross-browser and cross-device testing, WordPress theme customization, page-speed optimization, Google Tag Manager setup, analytics snippet integration, A/B test implementation. Turns design comps into pixel-perfect, performant web pages.", + reportsToIndex: 5, + connectorPreference: "codex_local", + budgetMonthlyUsd: 180, + }, + + // ── Digital Marketing (8-10) ── + { + // index 8 + name: "Tariq Benali", + role: "Head of Digital", + title: "Head of Digital Marketing", + department: "marketing", + capabilities: "Integrated digital-marketing strategy, channel-budget allocation, campaign planning and performance review, marketing-tech stack management (HubSpot, Google Analytics 4, Hotjar), client reporting and insights, team mentoring, pitch support for new-business proposals. Orchestrates all digital-marketing efforts for client accounts.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + { + // index 9 + name: "Jessica Torres", + role: "SEO & Content Specialist", + title: "SEO & Content Specialist", + department: "marketing", + capabilities: "Technical SEO audits, keyword research and clustering (Ahrefs, Semrush), on-page optimization, content-brief creation, link-building strategy, local SEO, Google Search Console monitoring, schema markup implementation, content-performance reporting, competitor SEO analysis. Drives organic traffic and search visibility for clients.", + reportsToIndex: 8, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + { + // index 10 + name: "Ryan Gallagher", + role: "Paid Media Specialist", + title: "Paid Media Specialist", + department: "marketing", + capabilities: "Google Ads (Search, Display, Shopping, Performance Max), Meta Ads, LinkedIn Ads, programmatic buying, bid-strategy optimization, audience segmentation and retargeting, conversion tracking and attribution, ad-creative testing, budget pacing, monthly performance reporting. Manages paid campaigns to hit CPA and ROAS targets.", + reportsToIndex: 8, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + + // ── Strategy & Content (11-12) ── + { + // index 11 + name: "Ananya Chakraborty", + role: "Strategy Lead", + title: "Digital Strategy Lead", + department: "product", + capabilities: "Discovery workshops and stakeholder interviews, competitive-landscape analysis, customer-persona development, user-journey mapping, content strategy and editorial calendars, conversion-funnel design, analytics setup and KPI definition, quarterly strategy reviews, pitch-deck strategy sections. Defines the strategic foundation that all creative and development work builds on.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + { + // index 12 + name: "Liam O'Sullivan", + role: "Copywriter", + title: "Senior Copywriter", + department: "product", + capabilities: "Website copywriting (headlines, body, CTAs), brand-voice development, tagline and naming workshops, blog-post and article authorship, email-campaign copy, social-media caption writing, ad-copy variations for A/B testing, SEO-informed copywriting, editing and proofreading, tone-of-voice guidelines. Crafts words that convert, inform, and inspire.", + reportsToIndex: 11, + connectorPreference: "claude_local", + budgetMonthlyUsd: 160, + }, + + // ── Operations (13) ── + { + // index 13 + name: "Sandra Muller", + role: "Project Manager", + title: "Senior Project Manager", + department: "operations", + capabilities: "Project planning and timeline creation, resource allocation across concurrent client projects, Asana/Monday/ClickUp workflow management, sprint facilitation, budget tracking and profitability analysis, risk identification and mitigation, client status reports, internal retrospectives, QA process coordination, scope-change documentation. Keeps every project on time, on budget, and on scope.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 150, + }, + ], + goals: [ + { + title: "Win and deliver flagship client projects", + description: "Pitch, scope, and deliver end-to-end digital projects (brand identity, website, marketing campaigns) for key clients. Measure success by client satisfaction, on-time delivery, and project profitability.", + }, + { + title: "Build repeatable creative and development processes", + description: "Establish standardized workflows for discovery, design, development, and launch. Create reusable templates, component libraries, and process documentation that improve quality and reduce delivery time.", + }, + { + title: "Grow retainer revenue through digital marketing results", + description: "Drive measurable marketing results (traffic, leads, revenue) for clients to convert project work into ongoing retainers. Track and report on SEO, paid media, and social KPIs monthly.", + }, + { + title: "Establish the agency as a thought leader", + description: "Publish case studies, blog posts, and social content showcasing the agency's work and expertise. Win industry awards, speak at conferences, and build a recognizable agency brand.", + }, + ], + }, + + // ── B2B SaaS Company ────────────────────────────────────────────────── + // A product-led B2B SaaS company with the complete set of functions + // needed to build, sell, and support a subscription software product. + { + id: "saas-company", + name: "B2B SaaS Company", + description: "A 20-agent B2B SaaS company with Engineering, Product, Sales, Marketing, Customer Success, and Finance teams. Built for the full SaaS lifecycle: build the product, acquire customers, drive adoption, and maximize net revenue retention.", + icon: "CloudArrowUp", + departments: [ + { id: "executive", name: "Executive Team", description: "Company strategy, fundraising, and cross-functional leadership." }, + { id: "engineering", name: "Engineering", description: "Product development, infrastructure, quality assurance, and data engineering." }, + { id: "product", name: "Product", description: "Product strategy, roadmap, feature specification, and analytics." }, + { id: "design", name: "Design", description: "User experience research, UI design, and design-system management." }, + { id: "marketing", name: "Marketing", description: "Demand generation, content, product marketing, and developer relations." }, + { id: "sales", name: "Sales", description: "Pipeline generation, enterprise sales, and partnerships." }, + { id: "customer_support", name: "Customer Success & Support", description: "Customer onboarding, retention, technical support, and expansion." }, + { id: "finance", name: "Finance", description: "SaaS metrics, billing operations, financial planning, and investor relations." }, + { id: "hr", name: "People", description: "Recruiting, onboarding, culture, and people operations." }, + ], + agents: [ + // ── Executive (0-1) ── + { + // index 0 + name: "Nathan Cross", + role: "CEO", + title: "Chief Executive Officer", + department: "executive", + capabilities: "Company vision and go-to-market strategy, fundraising and investor relations, board management, executive hiring, pricing and packaging decisions, strategic partnerships, company culture, crisis management. The ultimate owner of the company's trajectory from seed to scale.", + reportsToIndex: null, + connectorPreference: "claude_local", + budgetMonthlyUsd: 230, + }, + { + // index 1 + name: "Samira Rostami", + role: "CTO", + title: "Chief Technology Officer", + department: "executive", + capabilities: "Technical vision and platform architecture, build-vs-buy decisions, engineering hiring bar, security posture, infrastructure cost management, technology radar, API strategy, integration ecosystem design. Owns the technical foundation that the entire business runs on.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 240, + }, + + // ── Engineering (2-6) ── + { + // index 2 + name: "Derek Johansson", + role: "VP Engineering", + title: "Vice President of Engineering", + department: "engineering", + capabilities: "Engineering management across product and platform teams, sprint planning and delivery metrics, technical-debt prioritization, code-review standards, incident response, engineering hiring pipeline, on-call scheduling, engineering budget management, architectural-review facilitation. Ensures engineering ships reliably every sprint.", + reportsToIndex: 1, + connectorPreference: "claude_local", + budgetMonthlyUsd: 210, + }, + { + // index 3 + name: "Priya Venkatesh", + role: "Senior Backend Engineer", + title: "Senior Backend Engineer", + department: "engineering", + capabilities: "Multi-tenant SaaS backend development, REST and GraphQL API design, PostgreSQL and Redis, background-job processing (Sidekiq, BullMQ), webhook delivery systems, RBAC and team-management features, billing integration (Stripe), audit-logging, rate limiting, database migration management, comprehensive testing. Builds the core SaaS platform backend.", + reportsToIndex: 2, + connectorPreference: "codex_local", + budgetMonthlyUsd: 250, + }, + { + // index 4 + name: "Luis Mendoza", + role: "Senior Frontend Engineer", + title: "Senior Frontend Engineer", + department: "engineering", + capabilities: "React/Next.js SaaS application development, TypeScript, state management (Zustand, React Query), data-table and dashboard components, real-time collaboration features, onboarding flow implementation, settings and admin panels, design-system consumption, accessibility (WCAG 2.2), Playwright E2E testing, performance optimization. Builds the product interface customers use every day.", + reportsToIndex: 2, + connectorPreference: "codex_local", + budgetMonthlyUsd: 240, + }, + { + // index 5 + name: "Anika Bergman", + role: "Platform Engineer", + title: "Senior Platform Engineer", + department: "engineering", + capabilities: "CI/CD pipeline (GitHub Actions), Infrastructure-as-Code (Terraform), Kubernetes cluster management, observability (Datadog, Sentry), secrets management, zero-downtime deployment, auto-scaling configuration, database backup and disaster recovery, SOC2 infrastructure controls, cost-per-customer analysis. Keeps the SaaS platform available, secure, and cost-efficient.", + reportsToIndex: 2, + connectorPreference: "codex_local", + budgetMonthlyUsd: 230, + }, + { + // index 6 + name: "Tomoko Sato", + role: "QA Engineer", + title: "Quality Assurance Engineer", + department: "engineering", + capabilities: "Test strategy for SaaS (multi-tenant edge cases, billing scenarios, RBAC permutations), test-automation framework (Playwright, Jest), CI quality gates, regression-test suites, performance load testing, accessibility testing, release-readiness checklists, bug-triage, cross-browser testing matrix. Quality gatekeeper for every release.", + reportsToIndex: 2, + connectorPreference: "codex_local", + budgetMonthlyUsd: 180, + }, + + // ── Product & Design (7-9) ── + { + // index 7 + name: "Rachel Abrams", + role: "VP Product", + title: "Vice President of Product", + department: "product", + capabilities: "Product vision and quarterly roadmap, pricing and packaging strategy, competitive analysis and feature benchmarking, PRD authorship, customer-interview synthesis, product analytics (Amplitude, Mixpanel), A/B test design, launch-readiness reviews, product-led growth strategy (free trial, onboarding, activation), integration-ecosystem prioritization. Decides what the product becomes next.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + // index 8 + name: "Jake Morrison", + role: "Product Manager", + title: "Product Manager", + department: "product", + capabilities: "Feature specification and user-story writing, sprint backlog grooming, customer-feedback triage (Productboard), data analysis for feature decisions, A/B test configuration, release-notes and changelog, beta-program management, competitive feature mapping, integration-partner coordination, go-to-market alignment with marketing. Turns user needs into shipped features.", + reportsToIndex: 7, + connectorPreference: "claude_local", + budgetMonthlyUsd: 170, + }, + { + // index 9 + name: "Chloe Laurent", + role: "Product Designer", + title: "Senior Product Designer", + department: "design", + capabilities: "User research (interviews, surveys, analytics review), wireframing and prototyping in Figma, UI design and design-system stewardship, usability testing, information architecture, onboarding-flow optimization, dashboard and data-visualization design, accessibility compliance, developer-handoff documentation. Ensures the product is intuitive and delightful to use.", + reportsToIndex: 7, + connectorPreference: "claude_local", + budgetMonthlyUsd: 170, + }, + + // ── Marketing (10-12) ── + { + // index 10 + name: "Ava Richardson", + role: "VP Marketing", + title: "Vice President of Marketing", + department: "marketing", + capabilities: "Integrated marketing strategy, demand-generation funnel design, marketing-budget allocation, brand positioning and messaging, marketing-ops tooling (HubSpot, Segment), campaign ROI analysis, product-marketing launch plans, competitive battlecard creation, analyst-relations. Drives pipeline and brand for the SaaS product.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 190, + }, + { + // index 11 + name: "Benjamin Hart", + role: "Content Marketing Manager", + title: "Content Marketing Manager", + department: "marketing", + capabilities: "Blog strategy and authorship, SEO keyword research and optimization, e-book and white-paper production, case-study writing, email-newsletter management, webinar content, social-media content calendar, thought-leadership ghostwriting, content-performance analytics. Produces content that drives organic traffic and educates buyers.", + reportsToIndex: 10, + connectorPreference: "claude_local", + budgetMonthlyUsd: 160, + }, + { + // index 12 + name: "Daria Volkov", + role: "Growth & Demand Gen Manager", + title: "Growth & Demand Generation Manager", + department: "marketing", + capabilities: "Paid-acquisition campaigns (Google, LinkedIn, Meta), marketing-automation workflows (lead scoring, nurture sequences), landing-page optimization, webinar and event marketing, referral-program management, product-led growth experiments (free-trial optimization, in-app messaging), attribution modeling, MQL-to-SQL conversion tracking. Fills the pipeline with qualified leads.", + reportsToIndex: 10, + connectorPreference: "claude_local", + budgetMonthlyUsd: 160, + }, + + // ── Sales (13-15) ── + { + // index 13 + name: "Carter Ellison", + role: "VP Sales", + title: "Vice President of Sales", + department: "sales", + capabilities: "Sales strategy and annual quota planning, sales-team hiring and enablement, pipeline inspection and forecasting, deal-desk governance, discount-approval authority, territory planning, compensation-plan design, sales-engineering coordination, quarterly business reviews, competitive win/loss analysis. Owns the revenue number.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 200, + }, + { + // index 14 + name: "Nina Johansson", + role: "Enterprise Account Executive", + title: "Enterprise Account Executive", + department: "sales", + capabilities: "Enterprise sales cycle management, multi-stakeholder deal navigation, custom demo and POC coordination, value-engineering and ROI presentations, security-questionnaire completion, contract negotiation, CRM pipeline accuracy, champion-building within accounts, cross-sell and upsell in install base, reference-customer development. Closes large deals by understanding buyer pain.", + reportsToIndex: 13, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + { + // index 15 + name: "Travis Chen", + role: "SDR Lead", + title: "Sales Development Representative Lead", + department: "sales", + capabilities: "Outbound-prospecting strategy and sequence design, cold-email and cold-call cadences, lead-qualification framework (BANT/MEDDPICC), CRM data enrichment (ZoomInfo, Apollo), meeting-booked metrics, SDR-to-AE handoff process, intent-signal monitoring, account-based outreach coordination, objection-handling scripts. Fills the top of the pipeline with qualified opportunities.", + reportsToIndex: 13, + connectorPreference: "claude_local", + budgetMonthlyUsd: 130, + }, + + // ── Customer Success & Support (16-18) ── + { + // index 16 + name: "Miriam Okafor", + role: "VP Customer Success", + title: "Vice President of Customer Success", + department: "customer_support", + capabilities: "Customer-retention strategy, health-score model design, churn-risk intervention, QBR facilitation, NPS and CSAT program management, onboarding-program design, expansion-revenue targets, voice-of-customer reporting to product, support-team management, customer-advisory-board coordination. Ensures customers adopt the product and renew.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 180, + }, + { + // index 17 + name: "Alex Petridis", + role: "Customer Success Manager", + title: "Customer Success Manager", + department: "customer_support", + capabilities: "Post-sale onboarding and implementation support, product-adoption tracking, proactive check-ins with assigned accounts, renewal forecasting, expansion and upsell identification, customer-training sessions, internal-champion cultivation, feature-request aggregation, success-plan creation, reference-customer development. The customer's partner inside the company.", + reportsToIndex: 16, + connectorPreference: "claude_local", + budgetMonthlyUsd: 140, + }, + { + // index 18 + name: "Rina Tanaka", + role: "Support Engineer", + title: "Technical Support Engineer", + department: "customer_support", + capabilities: "Tier-1 and Tier-2 technical support, bug reproduction and engineering escalation, knowledge-base article creation, Zendesk/Intercom workflow management, SLA-adherence tracking, API-integration troubleshooting, customer-facing incident communication, internal runbook creation, canned-response optimization. Resolves customer issues quickly and feeds patterns back to engineering.", + reportsToIndex: 16, + connectorPreference: "codex_local", + budgetMonthlyUsd: 140, + }, + + // ── Finance & People (19) ── + { + // index 19 + name: "Henrik Larsen", + role: "Head of Finance", + title: "Head of Finance", + department: "finance", + capabilities: "SaaS-metrics tracking (ARR, MRR, NRR, churn, CAC, LTV), financial planning and modeling, billing-operations management (Stripe, invoicing), budget-vs-actual reporting, fundraising data-room preparation, revenue recognition (ASC 606), expense-policy enforcement, vendor-contract negotiation, board-financial reporting, cash-runway monitoring. The financial engine of the SaaS business.", + reportsToIndex: 0, + connectorPreference: "claude_local", + budgetMonthlyUsd: 170, + }, + ], + goals: [ + { + title: "Ship a product customers love", + description: "Engineering, Product, and Design collaborate to build, ship, and iterate on the core SaaS product. Measure success by product adoption, feature usage, and customer satisfaction (NPS > 50).", + }, + { + title: "Build a repeatable sales engine", + description: "Sales, Marketing, and Customer Success create a predictable pipeline-to-revenue machine. Track CAC payback period, MQL-to-SQL conversion, and sales-cycle length. Achieve predictable quarterly revenue growth.", + }, + { + title: "Maximize net revenue retention", + description: "Customer Success drives onboarding, adoption, and expansion. Target net revenue retention above 120% by reducing churn, increasing product adoption, and identifying upsell opportunities within the install base.", + }, + { + title: "Drive efficient demand generation", + description: "Marketing generates qualified pipeline through content, paid acquisition, and product-led growth. Track marketing-sourced pipeline, content engagement, and free-trial-to-paid conversion rates.", + }, + { + title: "Build scalable engineering infrastructure", + description: "Platform engineering delivers CI/CD, monitoring, auto-scaling, and SOC2 compliance. Target 99.95% uptime, sub-200ms API latency at p95, and deployment frequency of multiple times per day.", + }, + { + title: "Achieve efficient growth economics", + description: "Finance and leadership monitor unit economics (CAC:LTV > 3:1, CAC payback < 18 months, gross margin > 75%). Maintain financial discipline while investing aggressively in growth.", + }, + ], + }, +]; + +export function getTemplate(id: string): CompanyTemplate | undefined { + return COMPANY_TEMPLATES.find(t => t.id === id); +} diff --git a/src/preload/index.ts b/src/preload/index.ts index c8fdf0c8..eadefec8 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -43,6 +43,45 @@ const api: DesktopApi = { setHeartbeat: (input) => ipcRenderer.invoke(IPC_CHANNELS.setHeartbeat, input), triggerHeartbeat: (input) => ipcRenderer.invoke(IPC_CHANNELS.triggerHeartbeat, input), getApiPort: () => ipcRenderer.invoke(IPC_CHANNELS.getApiPort), + getInbox: (companyId) => ipcRenderer.invoke(IPC_CHANNELS.getInbox, companyId), + dismissInboxItem: (input) => ipcRenderer.invoke(IPC_CHANNELS.dismissInboxItem, input), + getCompanyMetrics: (input) => ipcRenderer.invoke(IPC_CHANNELS.getCompanyMetrics, input), + getAgentMetrics: (input) => ipcRenderer.invoke(IPC_CHANNELS.getAgentMetrics, input), + getStandupReport: (companyId) => ipcRenderer.invoke(IPC_CHANNELS.getStandupReport, companyId), + applyCompanyTemplate: (input) => ipcRenderer.invoke(IPC_CHANNELS.applyCompanyTemplate, input), + listCompanyTemplates: () => ipcRenderer.invoke(IPC_CHANNELS.listCompanyTemplates), + saveSocialAccount: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveSocialAccount, input), + deleteSocialAccount: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteSocialAccount, input), + listBrowserActions: (input) => ipcRenderer.invoke(IPC_CHANNELS.listBrowserActions, input), + cancelBrowserAction: (input) => ipcRenderer.invoke(IPC_CHANNELS.cancelBrowserAction, input), + triggerBrowserLogin: (id) => ipcRenderer.invoke(IPC_CHANNELS.triggerBrowserLogin, id), + // Meetings + saveMeeting: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveMeeting, input), + deleteMeeting: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteMeeting, input), + // Documents + saveDocument: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveDocument, input), + deleteDocument: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteDocument, input), + // Knowledge Base + saveKnowledgeEntry: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveKnowledgeEntry, input), + deleteKnowledgeEntry: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteKnowledgeEntry, input), + // Sprints + saveSprint: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveSprint, input), + deleteSprint: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteSprint, input), + // Automation + saveAutomationRule: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveAutomationRule, input), + deleteAutomationRule: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteAutomationRule, input), + toggleAutomationRule: (input) => ipcRenderer.invoke(IPC_CHANNELS.toggleAutomationRule, input), + getAutomationLog: (companyId) => ipcRenderer.invoke(IPC_CHANNELS.getAutomationLog, companyId), + // Agent Messages + sendAgentMessage: (input) => ipcRenderer.invoke(IPC_CHANNELS.sendAgentMessage, input), + listAgentMessages: (input) => ipcRenderer.invoke(IPC_CHANNELS.listAgentMessages, input), + markMessageRead: (input) => ipcRenderer.invoke(IPC_CHANNELS.markMessageRead, input), + // Workflows + saveWorkflow: (input) => ipcRenderer.invoke(IPC_CHANNELS.saveWorkflow, input), + deleteWorkflow: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteWorkflow, input), + runWorkflow: (input) => ipcRenderer.invoke(IPC_CHANNELS.runWorkflow, input), + // Auto-assign + autoAssignTask: (input) => ipcRenderer.invoke(IPC_CHANNELS.autoAssignTask, input), subscribe: (callback: (event: DesktopEvent) => void) => { const listener = (_event: Electron.IpcRendererEvent, payload: DesktopEvent) => callback(payload); ipcRenderer.on(IPC_CHANNELS.rendererEvent, listener); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index ed8f08fc..fa56990a 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -2,21 +2,33 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { ArrowClockwise, + Bell, + BookOpen, + Brain, + CalendarBlank, CaretUpDown, + ChartBar, + ChatCircle, Check, Command, Cpu, CurrencyCircleDollar, + FileText, FolderOpen, Gear, + Heartbeat, Kanban, + Lightning, ListChecks, MagnifyingGlass, + Megaphone, Plus, Pulse, Robot, + Rows, ShieldCheck, Trash, + UserPlus, SquaresFour, Target, TreeStructure, @@ -27,14 +39,23 @@ import { X, } from "@phosphor-icons/react"; import type { + AgentMessageRecord, + AutomationRuleRecord, CompanyRecord, + CompanyMetrics, DesktopEvent, + InboxItem, ProfileSnapshot, RunRecord, SectionId, + SocialAccountRecord, + BrowserActionRecord, + StandupReport, + WorkflowPipelineRecord, } from "@shared/types"; import type { SidebarBadges } from "@shared/contracts"; import { CompanyOnboarding } from "./components/CompanyOnboarding"; +import { Inbox, StandupView, PerformanceView } from "./components/inbox"; import { ConnectorEditor } from "./components/connectors"; import { Dashboard, KanbanBoard } from "./components/dashboard"; import { EntityWorkspace } from "./components/EntityWorkspace"; @@ -65,6 +86,10 @@ import { ProjectEditor, ProjectList, } from "./components/organization"; +import { SocialAccountsPanel } from "./components/social"; +import { AutomationPanel } from "./components/automation"; +import { CommunicationHub } from "./components/communication"; +import { HiringPipeline } from "./components/hiring"; import { ActionButton, ErrorBoundary, ListFrame, ListRow, Modal, Panel, SectionButton, Select, StatusPill, useFocusTrap, useReducedMotion } from "./components/ui"; import { unwrap } from "./lib/desktop"; import { formatMoney, formatTime } from "./lib/formatters"; @@ -77,6 +102,9 @@ const navGroups: NavGroup[] = [ label: "Overview", items: [ { id: "overview", label: "Dashboard", icon: SquaresFour }, + { id: "inbox", label: "Inbox", icon: Bell }, + { id: "standup", label: "Standup", icon: Heartbeat }, + { id: "performance", label: "Performance", icon: ChartBar }, ], }, { @@ -84,17 +112,35 @@ const navGroups: NavGroup[] = [ items: [ { id: "agents", label: "Agents", icon: Robot }, { id: "orgchart", label: "Org Chart", icon: TreeStructure }, + { id: "hiring", label: "Hiring", icon: UserPlus }, + { id: "communication", label: "Messages", icon: ChatCircle }, { id: "goals", label: "Goals", icon: Target }, { id: "projects", label: "Projects", icon: Kanban }, { id: "tasks", label: "Tasks", icon: ListChecks }, + { id: "sprints", label: "Sprints", icon: Rows }, + ], + }, + { + label: "Automation", + items: [ + { id: "automation", label: "Automation", icon: Lightning }, + { id: "approvals", label: "Approvals", icon: ShieldCheck }, + ], + }, + { + label: "Knowledge", + items: [ + { id: "documents", label: "Documents", icon: FileText }, + { id: "knowledge", label: "Knowledge", icon: Brain }, + { id: "meetings", label: "Meetings", icon: CalendarBlank }, ], }, { label: "Operations", items: [ - { id: "approvals", label: "Approvals", icon: ShieldCheck }, { id: "costs", label: "Costs", icon: CurrencyCircleDollar }, { id: "activity", label: "Activity", icon: Pulse }, + { id: "social", label: "Social Media", icon: Megaphone }, ], }, { @@ -135,6 +181,11 @@ export function App() { const [badges, setBadges] = useState(null); const [commandPaletteSearch, setCommandPaletteSearch] = useState(""); const [showOnboarding, setShowOnboarding] = useState(false); + const [inboxItems, setInboxItems] = useState([]); + const [companyMetrics, setCompanyMetrics] = useState(null); + const [standupReport, setStandupReport] = useState(null); + const [socialAccounts, setSocialAccounts] = useState([]); + const [browserActions, setBrowserActions] = useState([]); const refresh = useCallback(async () => { try { @@ -147,6 +198,7 @@ export function App() { if (next.currentCompanyId) { const badgeResult = await window.agentCompany.getSidebarBadges(next.currentCompanyId); if (badgeResult.ok) setBadges(badgeResult.data); + setSocialAccounts(next.socialAccounts?.filter(a => a.companyId === next.currentCompanyId) ?? []); } setError(null); } catch (nextError) { @@ -196,6 +248,36 @@ export function App() { root.style.colorScheme = snapshot.theme === "system" ? "light dark" : snapshot.theme; }, [snapshot]); + useEffect(() => { + if (!snapshot?.currentCompanyId) return; + const companyId = snapshot.currentCompanyId; + + if (section === "inbox") { + void window.agentCompany.getInbox(companyId).then((result) => { + if (result.ok) setInboxItems(result.data); + }); + } + + if (section === "performance") { + void window.agentCompany.getCompanyMetrics({ companyId, days: 30 }).then((result) => { + if (result.ok) setCompanyMetrics(result.data); + }); + } + + if (section === "standup") { + void window.agentCompany.getStandupReport(companyId).then((result) => { + if (result.ok) setStandupReport(result.data); + }); + } + + if (section === "social") { + setSocialAccounts(snapshot.socialAccounts?.filter(a => a.companyId === companyId) ?? []); + void window.agentCompany.listBrowserActions({ companyId, limit: 50 }).then(result => { + if (result.ok) setBrowserActions(result.data); + }); + } + }, [section, snapshot?.currentCompanyId]); + // Global Escape key handler for command palette useEffect(() => { if (!commandPaletteOpen) return; @@ -233,11 +315,13 @@ export function App() { const selectedProject = companyProjects.find((p) => p.id === selectedProjectId) ?? null; // Sidebar badge mapping + const activeInboxCount = useMemo(() => inboxItems.filter((item) => !item.dismissed).length, [inboxItems]); const navBadges = useMemo>>(() => badges ? { + inbox: activeInboxCount > 0 ? { count: activeInboxCount, tone: "warn" } : undefined, tasks: badges.todoTasks > 0 ? { count: badges.todoTasks, tone: "default" } : undefined, approvals: badges.pendingApprovals > 0 ? { count: badges.pendingApprovals, tone: "warn" } : undefined, costs: badges.budgetWarnings > 0 ? { count: badges.budgetWarnings, tone: "danger" } : undefined, - } : {}, [badges]); + } : {}, [badges, activeInboxCount]); const liveRuns = useMemo(() => runs.filter((run) => run.status === "running" || run.status === "queued"), [runs]); const failedRuns = useMemo(() => runs.filter((run) => ["failed", "timed_out", "interrupted"].includes(run.status)), [runs]); @@ -542,6 +626,57 @@ export function App() { activity={filteredActivity} costs={companyCosts} connectors={snapshot.connectors} + meetings={snapshot.meetings?.filter((m) => !currentCompany || m.companyId === currentCompany.id)} + sprints={snapshot.sprints?.filter((s) => !currentCompany || s.companyId === currentCompany.id)} + onNavigate={(targetSection, entityId) => { + setSection(targetSection); + if (entityId) { + if (targetSection === "agents") setSelectedAgentId(entityId); + else if (targetSection === "tasks") setSelectedTaskId(entityId); + else if (targetSection === "approvals") setSelectedApprovalId(entityId); + else if (targetSection === "goals") setSelectedGoalId(entityId); + else if (targetSection === "projects") setSelectedProjectId(entityId); + } + }} + /> + ) : null} + + {section === "inbox" ? ( + { + setSection(targetSection); + if (entityId) { + if (targetSection === "agents") setSelectedAgentId(entityId); + else if (targetSection === "tasks") setSelectedTaskId(entityId); + else if (targetSection === "approvals") setSelectedApprovalId(entityId); + else if (targetSection === "goals") setSelectedGoalId(entityId); + else if (targetSection === "projects") setSelectedProjectId(entityId); + } + }} + onDismiss={(_itemId) => { /* no-op for now */ }} + /> + ) : null} + + {section === "standup" ? ( + { + setSection(targetSection); + if (entityId) { + if (targetSection === "agents") setSelectedAgentId(entityId); + else if (targetSection === "tasks") setSelectedTaskId(entityId); + else if (targetSection === "approvals") setSelectedApprovalId(entityId); + else if (targetSection === "goals") setSelectedGoalId(entityId); + else if (targetSection === "projects") setSelectedProjectId(entityId); + } + }} + /> + ) : null} + + {section === "performance" ? ( + { setSection(targetSection); if (entityId) { @@ -625,6 +760,16 @@ export function App() { { setSelectedAgentId(id); setSection("agents"); }} /> ) : null} + {section === "hiring" && currentCompany ? ( + a.companyId === currentCompany!.id)} + onRefresh={refresh} + onNavigateToAgent={(id) => { setSelectedAgentId(id); setSection("agents"); }} + /> + ) : null} + {section === "goals" ? ( <> setSelectedGoalId("__new__")} />}> @@ -794,6 +939,192 @@ export function App() { ) : null} + {section === "social" && snapshot?.currentCompanyId && ( + ({ id: a.id, name: a.name }))} + companyId={snapshot.currentCompanyId} + onSave={async (input) => { + await window.agentCompany.saveSocialAccount({ + companyId: input.companyId, + platform: input.platform, + accountName: input.accountName, + displayName: input.displayName, + requireApproval: input.requireApproval, + profileUrl: "", + status: "login_required", + metadataJson: "{}", + }); + await refresh(); + }} + onDelete={async (id) => { + if (!snapshot?.currentCompanyId) return; + await window.agentCompany.deleteSocialAccount({ id, companyId: snapshot.currentCompanyId }); + await refresh(); + }} + onLogin={async (accountId) => { + await window.agentCompany.triggerBrowserLogin(accountId); + }} + onNavigate={(s, id) => { + setSection(s); + if (id) { + if (s === "agents") setSelectedAgentId(id); + else if (s === "tasks") setSelectedTaskId(id); + else if (s === "approvals") setSelectedApprovalId(id); + else if (s === "goals") setSelectedGoalId(id); + else if (s === "projects") setSelectedProjectId(id); + } + }} + /> + )} + + {section === "automation" && currentCompany ? ( + r.companyId === currentCompany.id) ?? []} + workflows={snapshot.workflows?.filter(w => w.companyId === currentCompany.id) ?? []} + agents={companyAgents} + companyId={currentCompany.id} + onRefresh={refresh} + /> + ) : null} + + {section === "communication" && currentCompany ? ( + m.companyId === currentCompany.id) ?? []} + agents={companyAgents} + companyId={currentCompany.id} + onRefresh={refresh} + /> + ) : null} + + {section === "documents" && currentCompany ? ( + {(snapshot.documents?.filter(d => d.companyId === currentCompany.id) ?? []).length} documents}> + {(snapshot.documents?.filter(d => d.companyId === currentCompany.id) ?? []).length === 0 ? ( +
+ +

No documents yet. Agents will create PRDs, specs, and reports as they work.

+
+ ) : ( + + {(snapshot.documents?.filter(d => d.companyId === currentCompany.id) ?? []).map(doc => ( + +
+
+
+ + {doc.title} +
+
+ {doc.type.replace(/_/g, " ")} + v{doc.version} + {doc.authorAgentId && by {companyAgents.find(a => a.id === doc.authorAgentId)?.name ?? "Unknown"}} +
+
+ +
+
+ ))} +
+ )} +
+ ) : null} + + {section === "knowledge" && currentCompany ? ( + {(snapshot.knowledgeBase?.filter(k => k.companyId === currentCompany.id) ?? []).length} entries}> + {(snapshot.knowledgeBase?.filter(k => k.companyId === currentCompany.id) ?? []).length === 0 ? ( +
+ +

No knowledge entries yet. Agents learn and document lessons as they work.

+
+ ) : ( + + {(snapshot.knowledgeBase?.filter(k => k.companyId === currentCompany.id) ?? []).map(entry => ( + +
+
+
+ + {entry.topic} +
+
+ {entry.category.replace(/_/g, " ")} + - + {entry.content.slice(0, 100)}{entry.content.length > 100 ? "..." : ""} +
+
+ +
+
+ ))} +
+ )} +
+ ) : null} + + {section === "meetings" && currentCompany ? ( + {(snapshot.meetings?.filter(m => m.companyId === currentCompany.id) ?? []).length} meetings}> + {(snapshot.meetings?.filter(m => m.companyId === currentCompany.id) ?? []).length === 0 ? ( +
+ +

No meetings scheduled. Agents will schedule standups, reviews, and planning sessions.

+
+ ) : ( + + {(snapshot.meetings?.filter(m => m.companyId === currentCompany.id) ?? []).map(meeting => ( + +
+
+
+ + {meeting.title} +
+
+ {meeting.type.replace(/_/g, " ")} + {new Date(meeting.scheduledAt).toLocaleString()} + {meeting.durationMinutes}min +
+
+ +
+
+ ))} +
+ )} +
+ ) : null} + + {section === "sprints" && currentCompany ? ( + {(snapshot.sprints?.filter(s => s.companyId === currentCompany.id) ?? []).length} sprints}> + {(snapshot.sprints?.filter(s => s.companyId === currentCompany.id) ?? []).length === 0 ? ( +
+ +

No sprints yet. Start a sprint to organize work into focused cycles.

+
+ ) : ( + + {(snapshot.sprints?.filter(s => s.companyId === currentCompany.id) ?? []).map(sprint => ( + +
+
+
+ + {sprint.name} +
+
+ {sprint.startDate} - {sprint.endDate} + {sprint.completedPoints}/{sprint.velocityPoints} pts +
+
+ +
+
+ ))} +
+ )} +
+ ) : null} + {section === "workspaces" ? ( = { + Cpu: Cpu, + Megaphone: Megaphone, + Flask: Flask, + Stack: Stack, + Code: Code, +}; export function CompanyOnboarding({ connectors, @@ -24,7 +39,7 @@ export function CompanyOnboarding({ connectors: ProfileSnapshot["connectors"]; onDone: () => Promise; }) { - const [step, setStep] = useState("company"); + const [step, setStep] = useState("choose"); const [companyName, setCompanyName] = useState(""); const [workspacePath, setWorkspacePath] = useState(""); const [autoApproveHires, setAutoApproveHires] = useState(true); @@ -33,10 +48,20 @@ export function CompanyOnboarding({ const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [createdCompanyId, setCreatedCompanyId] = useState(null); + const [selectedTemplateId, setSelectedTemplateId] = useState(null); + const [templates, setTemplates] = useState([]); const hasReadyConnector = connectors.some((c) => c.status === "ready" || c.status === "degraded"); const readyConnectors = connectors.filter((c) => c.status === "ready" || c.status === "degraded"); const bestConnector = readyConnectors[0] ?? null; + const selectedTemplate = templates.find((t) => t.id === selectedTemplateId) ?? null; + + // Load templates + useEffect(() => { + window.agentCompany.listCompanyTemplates().then((result) => { + if (result.ok) setTemplates(result.data); + }); + }, []); async function pickDirectory() { setError(null); @@ -50,6 +75,54 @@ export function CompanyOnboarding({ } } + // Template launch + const launchFromTemplate = useCallback(async () => { + if (loading || !selectedTemplate) return; + if (!companyName.trim()) { + setError("Enter a company name."); + return; + } + if (!hasReadyConnector) { + setError("At least one connector must be ready."); + return; + } + setLoading(true); + setError(null); + try { + const companyId = unwrap( + await window.agentCompany.applyCompanyTemplate({ + templateId: selectedTemplate.id, + companyName: companyName.trim(), + companyDescription: "", + connectorId: bestConnector?.id, + }), + ); + + // Create workspace for the company if path provided + if (workspacePath) { + unwrap( + await window.agentCompany.saveWorkspace({ + companyId, + name: "Default Workspace", + localPath: workspacePath, + repoUrl: "", + repoRef: "", + isPrimary: true, + }), + ); + } + + setCreatedCompanyId(companyId); + setStep("launch"); + await onDone(); + } catch (submitError) { + setError(submitError instanceof Error ? submitError.message : "Unable to create the company."); + } finally { + setLoading(false); + } + }, [loading, selectedTemplate, companyName, hasReadyConnector, workspacePath, bestConnector, onDone]); + + // Custom company creation (existing flow) const createCompany = useCallback(async () => { if (loading) return; if (!companyName.trim()) { @@ -98,7 +171,6 @@ export function CompanyOnboarding({ setLoading(true); setError(null); try { - // Create goal if (goalTitle.trim()) { unwrap( await window.agentCompany.saveGoal({ @@ -111,8 +183,6 @@ export function CompanyOnboarding({ }), ); } - - // Create CEO agent automatically if (bestConnector) { const ceoId = unwrap( await window.agentCompany.saveAgent({ @@ -129,22 +199,14 @@ export function CompanyOnboarding({ status: "idle", }), ); - - // Enable heartbeat on CEO for autonomous operation unwrap(await window.agentCompany.setHeartbeat({ agentId: ceoId, companyId: createdCompanyId, enabled: true, intervalSec: 120 })); - setStep("launch"); - - // Auto-trigger the CEO's first run after a brief delay for UI setTimeout(async () => { try { await window.agentCompany.triggerHeartbeat({ agentId: ceoId, companyId: createdCompanyId, trigger: "assignment" }); - } catch { - // Non-fatal — CEO will wake on next heartbeat cycle - } + } catch { /* Non-fatal */ } }, 1500); } - await onDone(); } catch (submitError) { setError(submitError instanceof Error ? submitError.message : "Unable to set up the company."); @@ -165,10 +227,22 @@ export function CompanyOnboarding({ } }, [loading, onDone]); - const stepIndex = step === "company" ? 0 : step === "goal" ? 1 : 2; + const stepLabels = step === "choose" || step === "template" + ? [ + { label: "Choose Setup", icon: Lightning }, + { label: "Configure", icon: Buildings }, + { label: "Launch", icon: Rocket }, + ] + : [ + { label: "Create Company", icon: Buildings }, + { label: "Set Mission", icon: Target }, + { label: "Launch", icon: Rocket }, + ]; + + const stepIndex = step === "choose" ? 0 : step === "template" ? 1 : step === "company" ? 0 : step === "goal" ? 1 : 2; return ( -
+
{/* Left panel — branding */}
- {[ - { label: "Create Company", icon: Buildings }, - { label: "Set Mission", icon: Target }, - { label: "Launch", icon: Rocket }, - ].map((s, i) => ( + {stepLabels.map((s, i) => (
{i > 0 ?
: null}
- {step === "company" ? ( + {/* ── Step: Choose Mode ────────────────────────── */} + {step === "choose" ? ( + +
+
How would you like to start?
+
Choose your setup
+
+ + {/* Template cards */} +
+
Quick Start — Choose a template
+
+ {templates.map((template) => { + const Icon = templateIcons[template.icon] ?? Cpu; + return ( + + ); + })} +
+
+ +
+
+ OR +
+
+ + {/* Custom option */} + + + ) : step === "template" && selectedTemplate ? ( + /* ── Step: Template Configuration ─────────────── */ + +
Template: {selectedTemplate.name}
+
Configure & Launch
+
+ +
+ +
+ +
+ + void pickDirectory()} icon={FolderOpen} /> +
+
+
+ + {/* Team preview */} +
+
Team that will be created
+
+ {selectedTemplate.agents.map((agent, i) => ( +
+
+ {agent.name.split(/\s+/).slice(0, 2).map(w => w[0]).join("").toUpperCase()} +
+ {agent.name} + — {agent.title} +
+ ))} +
+
+ + + {selectedTemplate.agents.length} agents + + + + {selectedTemplate.goals.length} goals + +
+
+ + {/* What happens */} +
+
What happens next
+
+
+ + All {selectedTemplate.agents.length} agents are created with roles and budgets +
+
+ + Goals are set and assigned to the {selectedTemplate.agents[0]?.role ?? "CEO"} +
+
+ + The team leader starts working immediately +
+
+
+ + {error ? ( +
+ {error} +
+ ) : null} +
+ + void launchFromTemplate()} + icon={Rocket} + tone="accent" + loading={loading} + disabled={!companyName.trim() || !hasReadyConnector} + /> +
+ + ) : step === "company" ? ( + /* ── Step: Custom Company ─────────────────────── */ -
Step 1 of 3
+
Custom Setup — Step 1 of 3
Create your company
@@ -281,8 +527,6 @@ export function CompanyOnboarding({ The directory where your agents will work. You can add more later.
- - {/* Auto-approve toggle */}