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.
This commit is contained in:
2026-10-22 08:30:40 +01:00
parent 7a3d2df213
commit 96735de48f
38 changed files with 551 additions and 135 deletions
@@ -1,14 +1,14 @@
import { useContext, useState } from "react";
import { AppContext } from "@/src/pages/__root";
import type { EnvsOverviewPayload } from "@/src/types";
import type { EnvsMutationResult, EnvsOverviewPayload } from "@/src/types";
import EnvsGroup from "../../(partials)/envs/envs-group";
import EnvScopeSelect from "../(partials)/env-scope-select";
import EnvsGroup from "../(partials)/envs-group";
import KnownEnvsGroup from "../(partials)/known-envs-group";
import scopeValue, { envInScope } from "../(utils)/scope-value";
/**
* Pick where (general, a workspace, a computer), then that scope's envs.
* General also lists the app settings. Every change saves at once and
* Pick where (general, a workspace, a bot or a computer), then that scope's
* envs. General also lists the app settings. Every change saves at once and
* refreshes the page data.
*/
export default function EnvironmentSettingsSection() {
@@ -26,8 +26,8 @@ export default function EnvironmentSettingsSection() {
const scope =
overview.scopes.find((s) => scopeValue({ scope: s }) == selected) || overview.scopes[0]!;
const changed = (next: EnvsOverviewPayload, msg: string) => {
setOverview(next);
const changed = (result: EnvsMutationResult, msg: string) => {
if (result.overview) setOverview(result.overview);
setStatus({ success: true, msg });
setVersion((prev) => prev + 1);
};