UNPKG

framework

Version:

The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.

81 lines 4.38 kB
import { readAllAgents } from '../store/index.js'; import { postDiscordWebhook } from './discord-webhook.js'; // The identity + diff live in the leaf `keys.ts` so the dashboard can share them (they are pure); // re-exported here so this stays the import site for anything that already reads them from the // module that defines `Activity`. export { activityKey } from './keys.js'; // The "New activity" feed (#627): the cross-project stream of run lifecycle transitions that // do NOT need the human — an agent started, an agent finished. It is the default-off notification // category, the counterpart to the interventions queue (which is the always-on "needs you" // half). Same shape and same "only genuinely new" diff as interventions.ts, so the browser // hook and the Discord watcher fold it into a baseline on first look and then notify once per // transition — you hear an agent kick off and an agent land, nothing when the page/daemon starts. /** How many recent agents per project to consider. Bounds the finished-set — older agents rolled off * long ago and were already baselined, so they never fire. A running agent is always newest (live * meta is prepended), so it is always in range. */ const RECENT_RUNS = 20; /** Map one agent to its current activity item: `started` while running, else `finished`. */ function activityFor(project, agent) { const kind = agent.status === 'running' ? 'started' : 'finished'; return { projectId: project.id, projectName: project.name, agentId: agent.id, kind, ...(agent.intent ? { title: agent.intent } : {}), ...(kind === 'finished' ? { status: agent.status } : {}), ...(agent.updatedAt ? { updatedAt: agent.updatedAt } : {}), }; } /** * Build the cross-project activity feed: for each registered project's most recent agents, one * item per agent reflecting where it is now (`started` while it runs, `finished` once it lands), * newest first. Forgiving — a project whose agents cannot be read simply contributes nothing, and * comes back named as one that was *not* read whole (#1623), since "nothing happened there" and * "I could not look" are the same empty list to everyone but the notification watcher. * * The `started` and `finished` items for one agent carry distinct keys ({@link activityKey}), so a * run that is still going notifies once (started) and again when it lands (finished). An agent that * both starts and finishes between two polls is only ever seen terminal, so it notifies once * (finished) — one quick agent, one line. */ export async function buildActivity(projects, deps = {}) { const readAgents = deps.readAgents ?? readAllAgents; const items = []; const whole = []; for (const project of projects) { const read = await readAgents(project.path).catch(() => undefined); if (!read) continue; whole.push(project.id); for (const agent of read.slice(0, RECENT_RUNS)) items.push(activityFor(project, agent)); } items.sort((a, b) => (b.updatedAt ?? '').localeCompare(a.updatedAt ?? '')); return { items, whole }; } /** * How one activity item reads on Discord: a started agent, or a finished one tagged by its outcome. * Beside {@link Activity} for the same reason {@link interventionLine} sits beside `Intervention` * — it switches on the kind, so it belongs with the type that declares the kinds. */ export function activityLine(item) { const what = item.title ?? 'a session'; if (item.kind === 'started') return `▶️ started: ${what}`; const mark = item.status === 'failed' ? '❌' : item.status === 'stopped' ? '⏹️' : '✅'; return `${mark} finished: ${what}`; } /** * Post the given activity items to a Discord webhook as one message, resolving whether Discord * accepted it (#940). `fetch` is injectable for tests. */ export async function postActivityDiscord(webhook, items, fetchImpl = fetch) { if (items.length === 0) return true; const content = items.length === 1 ? `📣 Activity (${items[0].projectName}): ${activityLine(items[0])}` : `📣 ${items.length} session updates:\n${items.map(i => `• ${i.projectName}: ${activityLine(i)}`).join('\n')}`; return postDiscordWebhook(webhook, content, fetchImpl); } //# sourceMappingURL=activity.js.map