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<AssignableAgent[]> {
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<string, AssignableAgent>();
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<AssignableAgent | null> {
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;
}