merge: integrate v7-rbac-visibility on top of skills feature

Brings together the two unmerged feature lines onto one integration branch:
- skills/review/proposals/revisions (via fix/mcpd-instance-health-and-retry,
  which contains the full skills-1..7 chain + mcpd env/retry/readiness fix)
- v7 visibility scope + ownership for Llms and Agents

Auto-merge resolved schema.prisma, mcpd main.ts, mcplocal config, CLI create,
and completions with no conflicts. Both Prisma migrations coexist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michal
2026-06-16 21:31:35 +01:00
24 changed files with 828 additions and 45 deletions

View File

@@ -264,6 +264,7 @@ export function createCreateCommand(deps: CreateCommandDeps): Command {
.option('--api-key-ref <ref>', 'API key reference in SECRET/KEY form (e.g. anthropic-key/token)')
.option('--extra <entry>', 'Extra config key=value (repeat)', collect, [])
.option('--pool-name <pool>', 'Stack with other Llms sharing this pool name; agents pinned to any member dispatch across the pool')
.option('--visibility <scope>', 'Visibility scope: public (everyone) or private (only owner + name-grants)', 'public')
.option('--force', 'Update if already exists')
.option('--skip-auth-check', 'Skip the upstream auth probe (for offline registration before infra exists)')
.action(async (name: string, opts) => {
@@ -276,6 +277,12 @@ export function createCreateCommand(deps: CreateCommandDeps): Command {
if (opts.url) body.url = opts.url;
if (opts.description !== undefined) body.description = opts.description;
if (opts.poolName !== undefined) body.poolName = opts.poolName;
if (opts.visibility !== undefined) {
if (opts.visibility !== 'public' && opts.visibility !== 'private') {
throw new Error(`Invalid --visibility '${opts.visibility as string}'. Expected 'public' or 'private'`);
}
body.visibility = opts.visibility;
}
if (opts.apiKeyRef) {
const slashIdx = (opts.apiKeyRef as string).indexOf('/');
if (slashIdx < 1) throw new Error(`Invalid --api-key-ref '${opts.apiKeyRef as string}'. Expected SECRET_NAME/KEY_NAME`);
@@ -333,6 +340,7 @@ export function createCreateCommand(deps: CreateCommandDeps): Command {
.option('--default-stop <text>', 'Default stop sequence (repeat for multiple)', collect, [])
.option('--default-extra <kv>', 'Default provider-specific knob k=v (repeat)', collect, [])
.option('--default-params-file <path>', 'Read defaultParams from a JSON file')
.option('--visibility <scope>', 'Visibility scope: public (everyone) or private (only owner + name-grants)', 'public')
.option('--force', 'Update if already exists')
.action(async (name: string, opts) => {
const body: Record<string, unknown> = {
@@ -341,6 +349,12 @@ export function createCreateCommand(deps: CreateCommandDeps): Command {
};
if (opts.project) body.project = { name: opts.project };
if (opts.description !== undefined) body.description = opts.description;
if (opts.visibility !== undefined) {
if (opts.visibility !== 'public' && opts.visibility !== 'private') {
throw new Error(`Invalid --visibility '${opts.visibility as string}'. Expected 'public' or 'private'`);
}
body.visibility = opts.visibility;
}
let systemPrompt = opts.systemPrompt as string | undefined;
if (systemPrompt === undefined && opts.systemPromptFile !== undefined) {

View File

@@ -138,6 +138,10 @@ interface LlmRow {
status?: 'active' | 'inactive' | 'hibernating';
// v4: explicit pool key. NULL = solo Llm (effective pool = its own name).
poolName?: string | null;
// v7: visibility scope. Legacy public rows omit it; mcpd defaults missing
// values to 'public' on serialization.
visibility?: 'public' | 'private';
ownerId?: string | null;
}
// v4: POOL column placed right after NAME so an operator can't miss
@@ -148,6 +152,7 @@ const llmColumns: Column<LlmRow>[] = [
{ header: 'POOL', key: (r) => (r.poolName !== null && r.poolName !== undefined && r.poolName !== '') ? r.poolName : '-', width: 18 },
{ header: 'KIND', key: (r) => r.kind ?? 'public', width: 8 },
{ header: 'STATUS', key: (r) => r.status ?? 'active', width: 12 },
{ header: 'VISIBILITY', key: (r) => r.visibility ?? 'public', width: 11 },
{ header: 'TYPE', key: 'type', width: 12 },
{ header: 'MODEL', key: 'model', width: 28 },
{ header: 'TIER', key: 'tier', width: 8 },
@@ -214,12 +219,15 @@ interface AgentRow {
// AgentService as the publishing mcplocal heartbeats and disconnects.
kind?: 'public' | 'virtual';
status?: 'active' | 'inactive';
// v7: visibility — same semantics as Llm. Public legacy agents omit it.
visibility?: 'public' | 'private';
}
const agentColumns: Column<AgentRow>[] = [
{ header: 'NAME', key: 'name' },
{ header: 'KIND', key: (r) => r.kind ?? 'public', width: 8 },
{ header: 'STATUS', key: (r) => r.status ?? 'active', width: 10 },
{ header: 'VISIBILITY', key: (r) => r.visibility ?? 'public', width: 11 },
{ header: 'LLM', key: (r) => r.llm.name, width: 24 },
{ header: 'PROJECT', key: (r) => r.project?.name ?? '-', width: 20 },
{ header: 'DESCRIPTION', key: (r) => truncate(r.description, 50) || '-', width: 50 },