import { appAgentKey, parseAppAgentKey, type AgentRef } from "./actor"; import { db, filter, findById, findFirst } from "./db"; import { orgPushersFilter } from "./orgs"; // The agents people approved (connected apps), as something issues can be assigned to and work // can be traced back to. See lib/actor.ts for how agents are named. export type AssignableAgent = AgentRef & { userId: string; username: string }; /** * Agents that can take on work in a repository: connected apps of its owner, collaborators and * organization members that may write to it. An issue assigned to one shows up in that agent's * get_my_tasks. */ export async function listAssignableAgents(repo: { id: string; ownerId: string }): Promise { const orgPushers = await orgPushersFilter("user", repo.ownerId); const rows = await (await db()).collection("oauth_grants").getFullList({ filter: filter( `permission = 'write' && refresh_expires_at > {:now} && (all_repos = true || repos ?= {:repoId}) && (user = {:ownerId} || user.collaborators_via_user.repo ?= {:repoId}${orgPushers ? ` || ${orgPushers}` : ""})`, { now: Date.now(), repoId: repo.id, ownerId: repo.ownerId }, ), expand: "client,user", sort: "created_at", }); const agents = new Map(); for (const r of rows) { const key = appAgentKey(r.client, r.user); agents.set(key, { key, name: r.expand?.client?.name ?? "Agent", userId: r.user, username: r.expand?.user?.username ?? "" }); } return [...agents.values()].sort((a, b) => a.name.localeCompare(b.name) || a.username.localeCompare(b.username)); } /** An agent by its key, with the person it acts for, whether or not it's still connected. */ export async function findAgent(key: string): Promise { const app = parseAppAgentKey(key); if (app) { const [client, user] = await Promise.all([findById("oauth_clients", app.clientId), findById("users", app.userId)]); return user ? { key, name: client?.name ?? "Disconnected agent", userId: user.id, username: user.username } : null; } const token = /^tok_([a-z0-9]{15})$/.exec(key); if (token) { const row = await findFirst("api_tokens", filter("id = {:id}", { id: token[1] }), { expand: "user" }); return row ? { key, name: row.name, userId: row.user, username: row.expand?.user?.username ?? "" } : null; } return null; }