// Multi-tenant workspace registry
//
// Each workspace has its own agent whitelist, rate limits, and config.
// The default workspace handles existing single-tenant deployments.
import { RateLimiter } from './rate-limiter.js';
export class Workspace {
    id;
    name;
    agentWhitelist;
    limiter;
    memberSet; // null = open
    constructor(cfg) {
        this.id = cfg.id;
        this.name = cfg.name;
        this.agentWhitelist = new Set(cfg.agents);
        this.limiter = new RateLimiter(cfg.rateLimit?.rate ?? 10, (cfg.rateLimit?.intervalSec ?? 60) * 1000, cfg.rateLimit?.burst ?? 15);
        this.memberSet = cfg.members?.length ? new Set(cfg.members) : null;
    }
    hasAgent(agent) {
        // Empty whitelist = unrestricted. Named whitelist enforces membership.
        if (this.agentWhitelist.size === 0)
            return true;
        return this.agentWhitelist.has(agent);
    }
    hasMember(userId) {
        if (!this.memberSet)
            return true; // open workspace
        return this.memberSet.has(userId);
    }
    allow(userKey) {
        return this.limiter.allow(userKey);
    }
}
export class WorkspaceRegistry {
    workspaces = new Map();
    defaultWorkspace;
    constructor() {
        // Default workspace is unrestricted (empty whitelist) so any registered
        // agent — including ACP-added custom agents — is reachable without
        // explicit workspace configuration.
        this.defaultWorkspace = new Workspace({
            id: 'default',
            name: 'Default',
            agents: [],
        });
        this.workspaces.set('default', this.defaultWorkspace);
    }
    add(cfg) {
        this.workspaces.set(cfg.id, new Workspace(cfg));
    }
    /**
     * Remove a workspace by id. Returns true on success, false if the id
     * doesn't exist or refers to the default workspace (which is created
     * by the constructor and used as a fallback — removing it would break
     * `resolve()` for users without an explicit assignment).
     */
    remove(id) {
        if (id === 'default')
            return false;
        return this.workspaces.delete(id);
    }
    get(id) {
        return this.workspaces.get(id);
    }
    /** Resolve workspace for a user. Prioritizes named workspaces over default. */
    resolve(userId) {
        for (const ws of this.workspaces.values()) {
            if (ws.id === 'default')
                continue; // default matches last
            if (ws.hasMember(userId))
                return ws;
        }
        return this.defaultWorkspace;
    }
    /** Load from config */
    load(config) {
        if (config.workspaces) {
            for (const cfg of config.workspaces) {
                this.add(cfg);
            }
        }
    }
    /**
     * Return summaries of every registered workspace, including the default.
     * Used by `/workspaces` and the Web settings UI.
     */
    list() {
        return Array.from(this.workspaces.values()).map((ws) => ({
            id: ws.id,
            name: ws.name,
            agents: Array.from(ws.agentWhitelist),
            members: ws.memberSet ? ws.memberSet.size : null,
            rateLimit: ws.limiter.config(),
        }));
    }
    /**
     * Full config snapshot for the settings UI, including the original
     * member list. Distinct from `list()`'s summary shape (which exposes
     * only a member count to preserve callers that don't need the IDs).
     */
    listFull() {
        return Array.from(this.workspaces.values()).map((ws) => ({
            id: ws.id,
            name: ws.name,
            agents: Array.from(ws.agentWhitelist),
            members: ws.memberSet ? Array.from(ws.memberSet) : [],
            rateLimit: ws.limiter.config(),
        }));
    }
    get default() { return this.defaultWorkspace; }
}
export const workspaceRegistry = new WorkspaceRegistry();
//# sourceMappingURL=workspace.js.map