mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 05:16:50 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents and board users operate inside a company-scoped control plane where permissions decide which mutating actions they can perform > - Company skills are part of the reusable agent-company setup surface, but skill mutation had been coupled to broader agent-creation authority > - That coupling meant importing or managing skills required a permission that also implies hiring power, which is broader than the operation needs > - Paperclip already has a grant-based permission vocabulary, so skill mutation should be authorized through a dedicated `skills:create` capability while preserving existing default behavior for trusted agents > - This pull request adds the skill creation permission contract, enforces it on company skill mutations, exposes it in agent permission management, and documents the changed CLI/API expectations > - The benefit is a narrower, auditable permission path for skill import/create/update/delete flows without forcing agents to receive broader agent-creation authority ## Linked Issues or Issue Description No public issue is linked. Problem: company skill mutation APIs were effectively tied to broader agent creation authority. This PR splits skill mutation authorization onto the public `skills:create` permission while keeping existing default skill creation behavior for agents unless explicitly disabled. Related public PR found during duplicate search: #5330. That PR uses an older `canManageSkills` shape; this PR implements the `skills:create` grant path instead. ## What Changed - Added `skills:create` to shared permission constants and agent permission types/validators as `canCreateSkills`. - Backfilled default human/member role grants for `skills:create`. - Updated company skill mutation routes to require board/user or agent access to `skills:create`, while preserving legacy/default agent behavior through `canCreateSkills` unless explicitly disabled. - Updated agent permission update handling, UI permission controls, duplicate-agent payloads, plugin SDK fixtures, and agent detail API surfaces for `canCreateSkills`. - Added regression coverage for skill route authorization, permission schema/default behavior, invite grants, omitted permission updates, and duplicate-agent payloads. - Updated CLI and Paperclip skill documentation for the new skill creation permission. ## Verification - `pnpm exec vitest run server/src/__tests__/agent-permissions-service.test.ts server/src/__tests__/agent-permissions-routes.test.ts server/src/__tests__/company-skills-routes.test.ts server/src/__tests__/invite-join-grants.test.ts ui/src/lib/duplicate-agent-payload.test.ts` — 5 files, 90 tests passed. - `pnpm --filter @paperclipai/shared typecheck && pnpm --filter @paperclipai/server typecheck && pnpm --filter @paperclipai/ui typecheck` — passed. - `pnpm test:run ...changed files...` was attempted first, but the stable wrapper rejects explicit file arguments; direct Vitest was used for the same targeted files. ## Risks - Moderate authorization risk: this changes the gate for company skill mutations, so the tests cover board grant checks, agent explicit grant checks, legacy default allowance, and explicit denial. - Migration/backfill risk is low: the migration only grants `skills:create` to existing human roles that already need broad management capability. - UI/API compatibility risk is low: `canCreateSkills` remains default-on for full agent permissions, and the update validator preserves omitted values so unrelated permission edits do not re-enable disabled skill creation. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex, GPT-5 coding agent with terminal/tool use enabled. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
86 lines
2.7 KiB
TypeScript
86 lines
2.7 KiB
TypeScript
// @vitest-environment node
|
|
import { describe, expect, it } from "vitest";
|
|
import { buildDuplicateAgentPayload, duplicateAgentName } from "./duplicate-agent-payload";
|
|
import type { AgentDetail } from "@paperclipai/shared";
|
|
|
|
const baseAgent: AgentDetail = {
|
|
id: "agent-1",
|
|
companyId: "company-1",
|
|
name: "Senior Product Engineer",
|
|
urlKey: "senior-product-engineer",
|
|
role: "engineer",
|
|
title: "Senior Product Engineer",
|
|
icon: "code",
|
|
status: "idle",
|
|
reportsTo: "manager-1",
|
|
capabilities: "Builds product features.",
|
|
adapterType: "codex_local",
|
|
adapterConfig: {
|
|
model: "gpt-5.3-codex",
|
|
instructionsBundleMode: "managed",
|
|
instructionsRootPath: "/tmp/original/instructions",
|
|
instructionsEntryFile: "AGENTS.md",
|
|
instructionsFilePath: "/tmp/original/instructions/AGENTS.md",
|
|
promptTemplate: "legacy prompt",
|
|
bootstrapPromptTemplate: "legacy bootstrap",
|
|
},
|
|
runtimeConfig: {
|
|
heartbeat: { enabled: true },
|
|
},
|
|
defaultEnvironmentId: "environment-1",
|
|
budgetMonthlyCents: 500,
|
|
spentMonthlyCents: 123,
|
|
pauseReason: null,
|
|
pausedAt: null,
|
|
permissions: { canCreateAgents: true, canCreateSkills: true },
|
|
lastHeartbeatAt: null,
|
|
metadata: { source: "test" },
|
|
createdAt: new Date("2026-05-10T00:00:00.000Z"),
|
|
updatedAt: new Date("2026-05-10T00:00:00.000Z"),
|
|
chainOfCommand: [],
|
|
access: {
|
|
canAssignTasks: true,
|
|
taskAssignSource: "explicit_grant",
|
|
membership: null,
|
|
grants: [],
|
|
},
|
|
};
|
|
|
|
describe("duplicate agent payload", () => {
|
|
it("suffixes duplicate names", () => {
|
|
expect(duplicateAgentName("Senior Product Engineer")).toBe("Senior Product Engineer Copy");
|
|
expect(duplicateAgentName(" ")).toBe("Agent Copy");
|
|
});
|
|
|
|
it("copies agent fields while removing original instruction paths", () => {
|
|
const payload = buildDuplicateAgentPayload(baseAgent, {
|
|
entryFile: "AGENTS.md",
|
|
files: {
|
|
"AGENTS.md": "You are a copy.",
|
|
},
|
|
});
|
|
|
|
expect(payload).toMatchObject({
|
|
name: "Senior Product Engineer Copy",
|
|
role: "engineer",
|
|
title: "Senior Product Engineer",
|
|
icon: "code",
|
|
reportsTo: "manager-1",
|
|
capabilities: "Builds product features.",
|
|
adapterType: "codex_local",
|
|
adapterConfig: { model: "gpt-5.3-codex" },
|
|
runtimeConfig: { heartbeat: { enabled: true } },
|
|
defaultEnvironmentId: "environment-1",
|
|
budgetMonthlyCents: 500,
|
|
permissions: { canCreateAgents: true, canCreateSkills: true },
|
|
metadata: { source: "test" },
|
|
instructionsBundle: {
|
|
entryFile: "AGENTS.md",
|
|
files: { "AGENTS.md": "You are a copy." },
|
|
},
|
|
});
|
|
expect(payload.adapterConfig).not.toHaveProperty("instructionsFilePath");
|
|
expect(payload.adapterConfig).not.toHaveProperty("promptTemplate");
|
|
});
|
|
});
|