feat: web prompt editor + agent personalities #58
Reference in New Issue
Block a user
Delete Branch "feat/web-prompt-editor-personalities"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Six-stage feature landing in one branch. Adds agent personalities
(VLAN-on-ethernet overlays of prompts on top of an existing agent) and a
browser-based prompt editor served by mcpd at
/ui.f60f00f):Prompt.agentId,Personality,PersonalityPrompt,Agent.defaultPersonalityId. Migration + 8 new prisma-level assertions.6b5bd78):PersonalityRepository+PersonalityService(with scope enforcement so you can't smuggle a foreign-project prompt into a personality),PromptService.findByAgent+ agentId on create/upsert.faef1e7):/api/v1/personalities/*,/api/v1/agents/:name/prompts,personality: <name>field on the chat body.chat.service.tsnow assemblesagent.systemPrompt + agent-direct + project + personality + systemAppend(additive, backwards-compatible by construction).9050918):mcpctl chat --personality,mcpctl create personality,mcpctl get/edit/delete personalities, regenerated fish + bash completions, chat banner shows the active personality.0010cc1): new@mcpctl/webworkspace package. Vite + React 19 + Monaco. Pages for projects/prompts, agents/direct-prompts, agents/personalities, personality detail with bind/unbind picker. PAT/session login, dark terminal theme.4cbf58d): mcpd serves/uivia@fastify/static.Dockerfile.mcpdbuilds the SPA in-place and copies it to/usr/share/mcpd/web.personality.smoke.test.tsexercises the live HTTP surface. Newdocs/personalities.md; cross-links fromagents.md,chat.md, README.How it composes at chat time
personalityselected by--personality <name>flag, falling back toagent.defaultPersonalityId. Without either, the resulting block is byte-identical to today's layout (regression test included).Test plan
pnpm --filter @mcpctl/mcpd exec vitest run→ 801/801 (was 777, +24 new)pnpm --filter @mcpctl/cli exec vitest run→ 430/430pnpm --filter @mcpctl/web exec vitest run→ 7/7 (new)pnpm --filter db exec vitest run tests/agent-schema.test.ts→ 16/16 (was 8, +8 new)bash fulldeploy.shrebuilds the mcpd Docker image with the SPA baked in, then run the newpersonality.smoke.test.tsagainst the deployed instance.https://mcpctl.ad.itaz.eu/ui/, paste a token, walk Projects → Agents → Personalities, create + bind a prompt, runmcpctl chat reviewer --personality grumpyand see the banner + overlay take effect.🤖 Generated with Claude Code
A Personality is a named overlay on top of an Agent — same agent, same LLM, but a different bundle of prompts injected into the system block at chat time. VLAN-on-ethernet semantics: ethernet still works without VLAN; with a VLAN tag, frames are segmented but still ethernet. Schema additions: - Prompt.agentId (nullable FK + index, cascade on delete) so prompts can attach directly to an agent without going through a project. - Personality { id, name, description, agentId, priority } with unique (name, agentId). - PersonalityPrompt join table with per-binding priority override. - Agent.defaultPersonalityId (SetNull on delete) so an agent can pick one personality as the default when no --personality flag is passed. Backwards-compatible by construction: every new column is nullable; existing rows are valid as-is; the chat.service systemBlock changes land in Stage 3. 8 new prisma-level assertions in agent-schema.test.ts cover unique constraints, cascade behavior, the SetNull on defaultPersonalityId, and shared-prompt-across-personalities. All 16 db tests pass; mcpd typecheck + 777 mcpd unit tests still green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Wires the schema landed in Stage 1 into the service layer. No HTTP routes yet — Stage 3 will register `/api/v1/...` endpoints and update chat.service to read agent-direct + personality prompts when building the system block. Repositories: - PersonalityRepository: CRUD + listPrompts/attach/detach bindings. - PromptRepository: findByAgent + findByNameAndAgent; create/update accept the new agentId column. findGlobal now also filters agentId=null so agent-direct prompts don't leak into global lists. - AgentRepository: defaultPersonalityId on create + connect/disconnect in update. Services: - PersonalityService: CRUD scoped per agent, plus attach/detach with scope enforcement — a prompt may bind only if it's agent-direct on the same agent, in the agent's project, or global. Foreign-project / foreign-agent attachments are rejected with 400. - PromptService: createPrompt / upsertByName accept agentId and resolve `agent: <name>`, with XOR-with-project guard. Adds listPromptsForAgent. - AgentService: defaultPersonality (by name on the agent's own personality set) round-trips through update + AgentView. Validation: - prompt.schema.ts: refine() rejects projectId+agentId together. - personality.schema.ts: new Create/Update/AttachPrompt schemas. - agent.schema.ts: defaultPersonality { name } | null on update. Tests: 12 PersonalityService + 7 PromptService agent-scope tests covering happy paths, XOR/scope enforcement, double-attach guard, detach-not-bound. mcpd suite: 796/796 (was 777). Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>New workspace package @mcpctl/web — a Vite + React 19 SPA that talks to mcpd's existing HTTP API. Bundles to a static dist/ which Stage 6 will bake into the RPM and serve from mcpd at /ui via @fastify/static. Pages: /ui/projects list projects /ui/projects/:name/prompts CRUD project prompts (Monaco editor) /ui/agents list agents /ui/agents/:name tabs: Direct prompts | Personalities /ui/personalities/:id bind/unbind prompts to a personality Auth: paste a session token (mcpctl auth login) or PAT (mcpctl_pat_*) once on a login screen, kept in localStorage; logout clears it. API client: 60-line fetch wrapper, attaches the bearer header from storage, throws an ApiError with status + parsed body on non-2xx. A 200-line useFetch hook provides loading/error/data without a state-management library — we are not building Notion. UX: - Dark terminal-adjacent theme so the page feels like the CLI. - Monaco @monaco-editor/react for prompt content (markdown mode, word-wrap, search, multi-cursor). - Personality detail's "attach prompt" picker filters in-scope candidates: agent-direct + same-project + globals. Dev loop: pnpm --filter @mcpctl/web dev (vite at :5173, proxies /api to https://mcpctl.ad.itaz.eu — override with MCPCTL_API_URL). Build: pnpm --filter @mcpctl/web build → src/web/dist/. Tests: 7 vitest cases covering the bearer header / 4xx body / 204 no-content path on the api wrapper, and the login storage round-trip + help toggle. Production build green: 269 KB JS / 84 KB gzipped. Typecheck clean (TS strict + exactOptionalPropertyTypes carried over). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>