Files
cobble/src/functions/backend/db/envs/grab-all-envs.ts
T
tben 96735de48f Let a bot and a workspace set their own envs
Envs had three scopes, general, workspace and computer. A bot had no
scope of its own, so the only way to give one worker a key was to set it
on a computer it happened to share.

The envs table gets a bot_id, and resolution now layers bot over
computer over workspace over general, so a bot's own key wins wherever it
runs. Every computer call site passes the run's bot_id, and grabRunEnvs
does the same for the {{env.KEY}} a custom tool or an MCP server's headers
read.

A bot's and a workspace's settings page can now edit their own envs, in
place, through the same save-env and delete-env routes: sending `scoped`
makes them answer with just that scope instead of the whole install's
overview. Super admins only, like the Environment page.
2026-10-22 08:30:40 +01:00

62 lines
1.9 KiB
TypeScript

import BunSQLite from "@moduletrace/bun-sqlite";
import type { BUN_SQLITE_COBBLE_ENVS } from "@/db/types/db";
import type { EnvParadigm } from "@/src/dict/env-dict";
import { decrypt } from "@/src/utils/crypt";
export type DecryptedEnv = {
id: number;
key: string;
value: string;
description: string;
paradigm: EnvParadigm;
workspace_id: number | null;
computer_id: number | null;
bot_id: number | null;
secret: boolean;
expose_to_computers: boolean;
};
let cache: Promise<DecryptedEnv[]> | undefined;
/**
* Every env, decrypted, cached in this process until `clearEnvCache` (every
* save and delete calls it). A value that can't be decrypted (the
* encryption key changed) reads as empty.
*/
export default function grabAllEnvs(): Promise<DecryptedEnv[]> {
cache ||= load().catch((error) => {
cache = undefined;
throw error;
});
return cache;
}
export function clearEnvCache() {
cache = undefined;
}
async function load() {
const rows =
(
await BunSQLite.sql<BUN_SQLITE_COBBLE_ENVS>({
sql: `SELECT * FROM envs ORDER BY id ASC`,
})
).payload || [];
return await Promise.all(
rows.map(async (row): Promise<DecryptedEnv> => ({
id: Number(row.id),
key: row.key || "",
value: row.value ? await decrypt(row.value).catch(() => "") : "",
description: row.description || "",
paradigm: (row.paradigm || "general") as EnvParadigm,
workspace_id: typeof row.workspace_id == "number" ? row.workspace_id : null,
computer_id: typeof row.computer_id == "number" ? row.computer_id : null,
bot_id: typeof row.bot_id == "number" ? row.bot_id : null,
secret: row.secret != 0,
expose_to_computers: row.expose_to_computers != 0,
})),
);
}