This guide details how Symphony components integrate with the permissions system to ensure secure access to files, features, and AI capabilities while maintaining a seamless user experience.
Components require various permissions to function properly:
// ComponentPermissions.ts
export const ComponentPermissions = {
// File system permissions
FILE_READ: 'fs.read',
FILE_WRITE: 'fs.write',
// Language server permissions
LSP_CONNECT: 'lsp.connect',
LSP_DIAGNOSTICS: 'lsp.diagnostics',
// AI model permissions
AI_COMPLETION: 'ai.completion',
AI_ANALYSIS: 'ai.analysis',
// Extension permissions
EXTENSION_API: 'component.extensionApi',
// Clipboard permissions
CLIPBOARD_READ: 'clipboard.read',
CLIPBOARD_WRITE: 'clipboard.write'
};
Components use a custom hook to manage permissions:
// useComponentPermissions.ts
import { usePermission } from 'frontend/shared/extension-bridge';
import { ComponentPermissions } from './ComponentPermissions';
export const useComponentPermissions = (resourcePath) => {
// File system permissions with path constraints
const fileRead = usePermission(ComponentPermissions.FILE_READ, {
scope: 'workspace',
paths: [resourcePath]
});
const fileWrite = usePermission(ComponentPermissions.FILE_WRITE, {
scope: 'workspace',
paths: [resourcePath]
});
// AI model permissions
const aiCompletion = usePermission(ComponentPermissions.AI_COMPLETION, {
scope: 'workspace',
paths: [resourcePath]
});
const aiAnalysis = usePermission(ComponentPermissions.AI_ANALYSIS, {
scope: 'workspace',
paths: [resourcePath]
});
// Extension API permissions
const extensionApi = usePermission(ComponentPermissions.EXTENSION_API);
// Clipboard permissions
const clipboardRead = usePermission(ComponentPermissions.CLIPBOARD_READ);
const clipboardWrite = usePermission(ComponentPermissions.CLIPBOARD_WRITE);
// Helper function to request all required permissions
const requestAllPermissions = async () => {
const results = await Promise.all([
fileRead.granted || fileRead.request(),
fileWrite.granted || fileWrite.request(),
aiCompletion.granted || aiCompletion.request(),
aiAnalysis.granted || aiAnalysis.request(),
extensionApi.granted || extensionApi.request(),
clipboardRead.granted || clipboardRead.request(),
clipboardWrite.granted || clipboardWrite.request()
]);
return results.every(Boolean);
};
return {
permissions: {
fileRead,
fileWrite,
aiCompletion,
aiAnalysis,
extensionApi,
clipboardRead,
clipboardWrite
},
requestAllPermissions,
allGranted: [
fileRead.granted,
fileWrite.granted,
aiCompletion.granted,
aiAnalysis.granted,
extensionApi.granted,
clipboardRead.granted,
clipboardWrite.granted
].every(Boolean)
};
};
Components use the permissions hook to control feature availability:
// SymphonyComponent.tsx with permissions
export const SymphonyComponent = ({ resourcePath, ...props }) => {
const {
permissions,
allGranted,
requestAllPermissions
} = useComponentPermissions(resourcePath);
const {
aiSuggestions,
requestAnalysis,
isProcessing
} = useAIModelIntegration();
// Request permissions when needed
const handleComponentMount = async () => {
if (!allGranted) {
const granted = await requestAllPermissions();
if (!granted) {
console.warn('Some component permissions were denied');
}
}
};
// Conditionally enable features based on permissions
const handleRequestAnalysis = async (data, context) => {
if (permissions.aiAnalysis.granted) {
return requestAnalysis(data, context);
} else {
const granted = await permissions.aiAnalysis.request();
if (granted) {
return requestAnalysis(data, context);
} else {
console.warn('AI analysis permission denied');
return null;
}
}
};
return (
<div className="symphony-component-container">
<Component
{...props}
resourcePath={resourcePath}
suggestions={permissions.aiCompletion.granted ? aiSuggestions : []}
onRequestAnalysis={handleRequestAnalysis}
isAIProcessing={isProcessing}
onMount={handleComponentMount}
readOnly={!permissions.fileWrite.granted}
/>
{/* Permission status indicators */}
<div className="component-permission-status">
{!permissions.fileWrite.granted && (
<div className="permission-badge read-only">
Read Only
</div>
)}
{!permissions.aiCompletion.granted && (
<div className="permission-badge ai-disabled">
AI Disabled
</div>
)}
</div>
</div>
);
};
When components are packaged as extensions, they declare required permissions:
{
"id": "symphony.component",
"name": "Symphony Component",
"version": "1.0.0",
"permissions": [
{
"id": "fs.read",
"reason": "Required to open and read files in your workspace"
},
{
"id": "fs.write",
"reason": "Required to save changes to files"
},
{
"id": "ai.completion",
"reason": "Required for AI-powered completion features"
},
{
"id": "ai.analysis",
"reason": "Required for AI-powered analysis"
},
{
"id": "component.extensionApi",
"reason": "Required to support component extensions"
},
{
"id": "clipboard.read",
"reason": "Required for paste operations"
},
{
"id": "clipboard.write",
"reason": "Required for copy operations"
}
]
}
Components leverage Symphony's AI models to provide context-aware permission requests:
// AIPermissionContext.tsx
import { useAIPermissionContext } from 'frontend/shared/extension-bridge';
export const AIPermissionContext = ({ children }) => {
const { enhancePermissionRequest } = useAIPermissionContext();
useEffect(() => {
enhancePermissionRequest('fs.write', async (context) => {
return {
explanation: `The component needs to save changes to ${context.resourcePath}`,
riskAnalysis: {
level: 'low',
reason: 'File is within your workspace and contains standard data'
},
recommendation: 'allow'
};
});
enhancePermissionRequest('ai.completion', async (context) => {
return {
explanation: `AI completion will analyze your data in ${context.resourcePath}`,
riskAnalysis: {
level: 'medium',
reason: 'Data will be processed by AI models'
},
dataUsage: 'Your data will be sent to AI models but not stored permanently',
recommendation: 'allow'
};
});
return () => {
// Cleanup
};
}, [enhancePermissionRequest]);
return children;
};
// Feature works with reduced functionality when permissions are denied
const handleFeature = async () => {
if (permissions.aiAnalysis.granted) {
return performAIAnalysis();
} else {
return performBasicAnalysis();
}
};